Skip to main content

Rendering Templates

Rendering is the process of combining your template structure with actual content (videos, images, text, captions) to produce a final video file.

How It Works

  1. You have a template with defined blocks (e.g., one video block, one subtitle block)
  2. At render time, you fill each block with content
  3. The render worker combines everything and outputs a video
  4. You poll the job until it completes and download the result
Template (structure)  +  Block Fills (content)  →  Rendered Video

Before You Render

Always inspect your template's blockSchema first. This tells you exactly what blocks are available, their types, and whether they're required.

curl -H "X-API-Key: YOUR_KEY" \
https://peako.shin0x.space/api/templates/YOUR_TEMPLATE_ID

The response includes a blockSchema:

{
"blockSchema": {
"hero-video": {
"blockId": "hero-video",
"type": "video",
"label": "Hero Video",
"required": true,
"durationMode": "locked",
"position": { "startSec": 0, "durationSec": 15 },
"trackIndex": 0
},
"subtitle-main": {
"blockId": "subtitle-main",
"type": "subtitle",
"label": "Subtitles",
"required": false,
"durationMode": "locked",
"position": { "startSec": 0, "durationSec": 15 },
"trackIndex": 1
}
}
}

Use this to build your blocks object for the render request. Every key in your blocks object must match a blockId from the template.

tip

Always call GET /api/templates/:id and inspect blockSchema before rendering. It tells you exactly which blocks exist, what type they are, whether they're required, and where they sit in the timeline.


Understanding Block Fills

A "block fill" is the content you provide for a block at render time. The type of content you can provide depends on the block type:

Block TypeAllowed Fills
videosrc_url, upload_url
imagesrc_url, upload_url
audiosrc_url, upload_url
texttext, rich_text
subtitletext, captions, auto_transcribe

Duration Modes

Each block has a durationMode that controls how the block duration is calculated:

ModeBehaviorUse Case
lockedBlock uses its template-defined duration. Asset is trimmed or padded to fit.Fixed-length sections (intro, outro, fixed video slots)
trim_to_assetBlock duration stretches to match the actual asset length. Useful for variable-length content.Content with unpredictable length
stretch_to_slotAsset is stretched/compressed to fill the block's defined duration.When you need exact timing regardless of asset length

Coordinate System

All position and size values use normalized coordinates (0.0–1.0):

  • (0.0, 0.0) = top-left corner
  • (1.0, 1.0) = bottom-right corner
  • (0.5, 0.5) = center of the canvas

Example positioning on a 1080×1920 (9:16) canvas:

┌────────────────────────────────────┐
│ (0, 0) (1, 0) │
│ │
│ x: 0.1, y: 0.1 (108, 192) │
│ width: 0.8, height: 0.3 (864, 576)│
│ ┌──────────────────────┐ │
│ │ Block content │ │
│ └──────────────────────┘ │
│ │
│ (0, 1) (1, 1) │
└────────────────────────────────────┘

Render Endpoint

POST /api/templates/:id/render

Path Parameters:

  • id — template ID

Headers:

  • X-API-Key: <your-api-key> (required)
  • Content-Type: application/json

Request Body:

{
"blocks": {
"hero-video": { ... },
"subtitle-main": { ... }
},
"outputFormat": "mp4",
"delivery": "async",
"canvasSize": {
"width": 1080,
"height": 1920
}
}

Top-Level Fields:

FieldTypeRequiredDescription
blocksobjectMap of blockId → BlockFill. Keys must match block IDs in the template.
outputFormatstring"mp4" (default), "webm", or "mov"
deliverystring"async" (default), "storage", or "response"
canvasSizeobjectOverride canvas size: { width, height } in pixels

Delivery Modes:

ModeBehaviorBest For
asyncReturns { jobId, status: "queued" } immediately. Poll GET /api/jobs/:jobId.Default. Recommended for any non-trivial render.
storageRenders and uploads result to CDN. Returns job ID and eventual outputUrl.When you want the result stored on the CDN.
responseStreams binary video directly in the HTTP response body. Only works for very short clips (under 50 MB).Short clips where you want immediate download.

Response (async/storage delivery):

{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued"
}

Status codes:

  • 202 — Job queued successfully
  • 400 — Invalid block fill, missing required block, validation failed
  • 401 — Missing or invalid API key
  • 404 — Template not found
  • 409 — Queue full (max 20 concurrent jobs per user)

Block Fill Types (6 Variants)

Each block in your render request is a BlockFill object. Depending on the block type and your use case, you can fill it in different ways.

1. URL-Based Fill (src_url)

Use for video, audio, or image blocks where the asset is at a CDN URL.

{
"src_url": "https://peako.shin0x.space/assets/video-uuid.mp4"
}
FieldTypeRequiredDescription
src_urlstringCDN URL of the asset. Must be from the Peako CDN (peako.shin0x.space/assets/...).

When to use: When you've already uploaded the file and have its CDN URL, or when referencing an external asset from the allowlist.

Example:

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"src_url": "https://peako.shin0x.space/assets/my-video-abc123.mp4"
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

2. Uploaded Asset Fill (upload_url)

Reference a file that was uploaded via POST /api/assets/upload. Functionally identical to src_url.

{
"upload_url": "https://peako.shin0x.space/assets/video-uuid.mp4"
}
FieldTypeRequiredDescription
upload_urlstringThe exact url returned from POST /api/assets/upload.

When to use: When you want to be explicit that the file was freshly uploaded in this session. Functionally the same as src_url.


3. Text Fill

For text or subtitle blocks — supply plain static text.

{
"text": "Welcome to our channel!"
}
FieldTypeRequiredDescription
textstringPlain text content. Supports \n for line breaks.

When to use:

  • Static text overlays or lower-thirds
  • Simple titles
  • For subtitles: a static caption displayed for the entire block duration (no timing)

Example:

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"title-block": {
"text": "Top 5 Tips for 2026"
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

4. Rich Text Fill (Styled)

For text or subtitle blocks — supply styled text with font, color, and positioning control.

{
"rich_text": {
"content": "Subscribe for more!",
"style": {
"fontFamily": "Inter",
"fontSize": 48,
"color": "#FFFFFF",
"bold": true,
"italic": false,
"align": "center"
},
"transform": {
"x": 0.1,
"y": 0.05,
"width": 0.8,
"height": 0.1,
"opacity": 1.0
}
}
}

Rich Text Fields:

FieldTypeRequiredDescription
rich_text.contentstringText to display
rich_text.styleobjectStyle options (see table below)
rich_text.transformobjectOverride block position/size (0.0–1.0 normalized coords)

Style Fields:

FieldTypeOptionsDescription
fontFamilystring"Inter", "Arial", "Georgia", "Courier", etc.Font name
fontSizenumberpixels, e.g., 32Font size
colorstringhex code, e.g., "#FFFFFF"Text color
boldbooleantrue / falseBold text
italicbooleantrue / falseItalic text
alignstring"left", "center", "right"Text alignment

Transform Fields (optional): Override the block's template position using the same 0.0–1.0 normalized coordinate system. If omitted, uses the block's template transform.

When to use: When you need specific fonts, colors, or text positioning that differs from the template defaults.

Example:

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"title-block": {
"rich_text": {
"content": "Amazing Title",
"style": {
"fontFamily": "Inter",
"fontSize": 56,
"color": "#FFD700",
"bold": true,
"align": "center"
},
"transform": {
"x": 0.05,
"y": 0.1,
"width": 0.9,
"height": 0.15
}
}
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

5. Captions Fill (Pre-Timed Subtitles)

For subtitle blocks — supply an array of timed caption entries. Each entry has a start time, end time, and text. All times are in milliseconds.

{
"captions": [
{ "id": "1", "from": 0, "to": 2500, "text": "Hey everyone, welcome back." },
{ "id": "2", "from": 2500, "to": 5000, "text": "Today we're covering the new API." },
{ "id": "3", "from": 5000, "to": 8200, "text": "Let's dive right in." }
],
"style": {
"fontFamily": "Arial",
"fontSize": 28,
"color": "#FFFFFF",
"align": "center"
},
"transform": {
"x": 0.05,
"y": 0.80,
"width": 0.90,
"height": 0.15
}
}

Caption Entry Fields:

FieldTypeRequiredDescription
idstringUnique ID for this caption entry (e.g., "1", "2")
fromnumberStart time in milliseconds from video start
tonumberEnd time in milliseconds from video start
textstringCaption text. Supports \n for multi-line.

Style Fields (optional): Same as Rich Text (fontFamily, fontSize, color, align).

Transform Fields (optional): Override the block's template position.

When to use:

  • You already have timestamps (from a previous transcription job or external tool)
  • You want to reuse captions across multiple renders
  • You need precise control over caption timing
note

All times are in milliseconds, not seconds.

  • 2.5 seconds = 2500
  • 1 minute = 60000
  • 1 hour 2 minutes 3.4 seconds = 3723400

Example:

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"upload_url": "https://peako.shin0x.space/assets/video-uuid.mp4"
},
"subtitle-main": {
"captions": [
{ "id": "1", "from": 0, "to": 2500, "text": "Welcome back to the channel." },
{ "id": "2", "from": 2500, "to": 5000, "text": "Hit subscribe if you'\''re new here." },
{ "id": "3", "from": 5000, "to": 8000, "text": "Let'\''s get into it." }
],
"style": {
"fontFamily": "Arial",
"fontSize": 32,
"color": "#FFFFFF",
"align": "center"
},
"transform": {
"x": 0.05,
"y": 0.78,
"width": 0.90,
"height": 0.18
}
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

6. Auto-Transcribe Fill (Most Powerful)

For subtitle blocks — automatically transcribe the audio from another block in the same render request. No SRT file needed. Transcription happens inside the render worker before rendering begins.

{
"auto_transcribe": true,
"transcribe_from": "hero-video"
}
FieldTypeRequiredDescription
auto_transcribebooleanMust be true
transcribe_fromstringblockId of a video or audio block in the same render request. That block must also be filled with src_url or upload_url.

How it works internally:

  1. The render worker downloads the source video/audio block's asset
  2. Extracts the audio track
  3. Runs faster-whisper (local speech-to-text) on the audio
  4. Generates a timed captions array from the transcription
  5. Injects the captions into the subtitle block
  6. Proceeds with normal rendering

Constraints:

  • transcribe_from must reference a video or audio block in the template
  • That block must be filled with a src_url or upload_url in the same render request
  • The render returns 202 immediately; transcription adds to processing time (typically 15-30 seconds for a 60-second video)
  • Languages are auto-detected; English and most major languages are supported

When to use:

  • You don't have pre-existing captions
  • You want subtitles generated automatically from spoken audio
  • You want the simplest possible integration — one request, no pre-processing steps
  • This is the recommended default for most use cases

Example:

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"upload_url": "https://peako.shin0x.space/assets/video-uuid.mp4"
},
"subtitle-main": {
"auto_transcribe": true,
"transcribe_from": "hero-video"
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

# Response:
# { "jobId": "job-uuid", "status": "queued" }

Subtitle Block — Deep Dive

The subtitle block is the most flexible block type. It can be filled in 4 distinct ways depending on your workflow:

When to Use Each Subtitle Fill

ScenarioFill TypeProsCons
Auto-generate from video audio — Don't have captions yet, want them automaticauto_transcribe: trueSimplest, one-step processAdds transcription time to render
Already transcribed — You have captions from a previous /api/transcribe call or external toolcaptions arrayReusable, no redundant transcription, precise controlRequires pre-transcription step
Have SRT file — You already have an .srt/.vtt/.ass fileParse with /api/subtitles/parse, then use captions arrayWorks with any existing subtitle file2-step process
Static text — Simple static caption for entire block duration (no timing)text fillSimplest syntaxNo timing, displays for full duration

Subtitle Block: 4 Complete Worked Examples

Example A — Auto-Transcribe (Simplest Path)

Use this when: You have a video with audio and want automatic captions.

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"upload_url": "https://peako.shin0x.space/assets/my-video.mp4"
},
"subtitle-main": {
"auto_transcribe": true,
"transcribe_from": "hero-video"
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

# Response:
# { "jobId": "abc123def456", "status": "queued" }

What happens:

  • System transcribes the video audio automatically
  • Generates timed captions
  • Burns them into the output
  • Takes slightly longer than a standard render (adds ~15-30s)

Example B — Pre-Timed Captions Array (Reusable)

Use this when: You already have the captions (from a previous /api/transcribe call, or from your own transcription pipeline).

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"upload_url": "https://peako.shin0x.space/assets/my-video.mp4"
},
"subtitle-main": {
"captions": [
{ "id": "1", "from": 0, "to": 2500, "text": "Welcome to the tutorial." },
{ "id": "2", "from": 2500, "to": 5800, "text": "Today we cover the render API." },
{ "id": "3", "from": 5800, "to": 9000, "text": "First, upload your video." },
{ "id": "4", "from": 9000, "to": 12500, "text": "Then fill in your blocks." },
{ "id": "5", "from": 12500,"to": 15000, "text": "Hit render. That'\''s it." }
],
"style": {
"fontFamily": "Inter",
"fontSize": 32,
"color": "#FFFFFF",
"align": "center"
},
"transform": {
"x": 0.05,
"y": 0.78,
"width": 0.90,
"height": 0.18
}
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

# Response:
# { "jobId": "abc123def456", "status": "queued" }

Advantages:

  • Reusable: You can use these same captions in multiple renders
  • Faster: No transcription time, just rendering
  • Flexible: You control every caption exactly (timing, styling, text)

Example C — From SRT File (Pre-Transcribed)

Use this when: You have an existing .srt/.vtt/.ass file with captions.

#!/bin/bash
API_KEY="your-api-key"
TEMPLATE_ID="your-template-id"

# Step 1: Parse your SRT file
PARSE_RESULT=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-F "file=@my-captions.srt" \
https://peako.shin0x.space/api/subtitles/parse)

# Step 2: Extract captions from response
CAPTIONS=$(echo "$PARSE_RESULT" | jq -c '.captions')

# Step 3: Render with parsed captions
curl -X POST \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"blocks\": {
\"hero-video\": {
\"upload_url\": \"https://peako.shin0x.space/assets/my-video.mp4\"
},
\"subtitle-main\": {
\"captions\": $CAPTIONS,
\"style\": {
\"fontFamily\": \"Arial\",
\"fontSize\": 28,
\"color\": \"#FFFFFF\",
\"align\": \"center\"
}
}
},
\"outputFormat\": \"mp4\",
\"delivery\": \"async\"
}" \
https://peako.shin0x.space/api/templates/$TEMPLATE_ID/render

Example SRT file (my-captions.srt):

1
00:00:00,000 --> 00:00:02,500
Welcome to the tutorial.

2
00:00:02,500 --> 00:00:05,800
Today we cover the render API.

3
00:00:05,800 --> 00:00:09,000
First, upload your video.

Example D — Static Text Caption (No Timing)

Use this when: You want simple static text displayed for the entire block duration (like a lower-third).

curl -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"blocks": {
"hero-video": {
"upload_url": "https://peako.shin0x.space/assets/my-video.mp4"
},
"subtitle-main": {
"text": "Subscribe for more videos!"
}
},
"outputFormat": "mp4",
"delivery": "async"
}' \
https://peako.shin0x.space/api/templates/TEMPLATE_ID/render

# Response:
# { "jobId": "abc123def456", "status": "queued" }

Note: This displays the text for the entire block duration without any timing controls. Use the captions array if you need granular timing.


Output Format & Canvas Options

Output Format

The outputFormat field controls the final video codec and container:

FormatCodecAudioUse Case
mp4 (default)H.264AACMaximum compatibility, streaming, web
webmVP8/VP9VorbisWeb, open-source projects
movProResPCMHigh-quality, editing workflows

Canvas Size Override

Override the template's canvas size in the render request:

{
"blocks": { ... },
"outputFormat": "mp4",
"delivery": "async",
"canvasSize": {
"width": 1920,
"height": 1080
}
}
FieldTypeDescription
widthnumberCanvas width in pixels
heightnumberCanvas height in pixels

Common resolutions:

  • 1920×1080 (1080p, 16:9)
  • 1080×1920 (9:16 vertical, mobile)
  • 720×720 (square, 1:1)
  • 1440×1440 (square, 1:1, high quality)

Complete End-to-End Examples

Example 1: Simple Video (No Subtitles)

Render a template with just a video fill — no subtitles, no effects.

#!/bin/bash
API_KEY="your-api-key"
TEMPLATE_ID="your-template-id"

# Step 1: Upload your video
echo "Uploading video..."
VIDEO_URL=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-F "file=@my-video.mp4" \
https://peako.shin0x.space/api/assets/upload | jq -r '.url')

echo "Video uploaded: $VIDEO_URL"

# Step 2: Render
echo "Rendering..."
JOB_ID=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"blocks\": {
\"hero-video\": { \"upload_url\": \"$VIDEO_URL\" }
},
\"outputFormat\": \"mp4\",
\"delivery\": \"async\"
}" \
https://peako.shin0x.space/api/templates/$TEMPLATE_ID/render | jq -r '.jobId')

echo "Job ID: $JOB_ID"

# Step 3: Poll until done
echo "Polling job status..."
while true; do
RESULT=$(curl -s -H "X-API-Key: $API_KEY" \
https://peako.shin0x.space/api/jobs/$JOB_ID)
STATUS=$(echo "$RESULT" | jq -r '.status')
echo "Status: $STATUS"

if [ "$STATUS" = "done" ]; then
OUTPUT=$(echo "$RESULT" | jq -r '.outputUrl')
echo "Done! Output: $OUTPUT"
break
elif [ "$STATUS" = "failed" ]; then
ERROR=$(echo "$RESULT" | jq -r '.error')
echo "Failed: $ERROR"
exit 1
fi

sleep 5
done

# Step 4: Download
curl -o output.mp4 "$OUTPUT"
echo "Saved as output.mp4"

Example 2: Video + Auto-Transcribed Subtitles

Automatically generate and burn-in captions from the video audio.

#!/bin/bash
API_KEY="your-api-key"
TEMPLATE_ID="your-template-id"

# Upload video
VIDEO_URL=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-F "file=@my-video.mp4" \
https://peako.shin0x.space/api/assets/upload | jq -r '.url')

# Render with auto-transcription
JOB_ID=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"blocks\": {
\"hero-video\": {
\"upload_url\": \"$VIDEO_URL\"
},
\"subtitle-main\": {
\"auto_transcribe\": true,
\"transcribe_from\": \"hero-video\"
}
},
\"outputFormat\": \"mp4\",
\"delivery\": \"async\"
}" \
https://peako.shin0x.space/api/templates/$TEMPLATE_ID/render | jq -r '.jobId')

echo "Job: $JOB_ID (transcription + render in progress...)"

# Poll
while true; do
RESULT=$(curl -s -H "X-API-Key: $API_KEY" \
https://peako.shin0x.space/api/jobs/$JOB_ID)
STATUS=$(echo "$RESULT" | jq -r '.status')
PROGRESS=$(echo "$RESULT" | jq -r '.progress // "N/A"')
echo "Status: $STATUS | Progress: $PROGRESS"

if [ "$STATUS" = "done" ]; then
OUTPUT=$(echo "$RESULT" | jq -r '.outputUrl')
curl -o output-with-captions.mp4 "$OUTPUT"
echo "Done! Saved as output-with-captions.mp4"
break
fi

[ "$STATUS" = "failed" ] && echo "Error: $(echo "$RESULT" | jq -r '.error')" && exit 1
sleep 5
done

Example 3: Video + Manual Captions Array

Use pre-existing captions from a transcription or external source.

#!/bin/bash
API_KEY="your-api-key"
TEMPLATE_ID="your-template-id"

VIDEO_URL=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-F "file=@my-video.mp4" \
https://peako.shin0x.space/api/assets/upload | jq -r '.url')

JOB_ID=$(curl -s -X POST \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"blocks\": {
\"hero-video\": {
\"upload_url\": \"$VIDEO_URL\"
},
\"subtitle-main\": {
\"captions\": [
{ \"id\": \"1\", \"from\": 0, \"to\": 2500, \"text\": \"Welcome back to the channel.\" },
{ \"id\": \"2\", \"from\": 2500, \"to\": 5000, \"text\": \"Hit subscribe if you'\''re new here.\" },
{ \"id\": \"3\", \"from\": 5000, \"to\": 8000, \"text\": \"Let'\''s get into it.\" }
],
\"style\": {
\"fontFamily\": \"Inter\",
\"fontSize\": 32,
\"color\": \"#FFFF00\",
\"align\": \"center\"
}
}
},
\"outputFormat\": \"mp4\",
\"delivery\": \"async\"
}" \
https://peako.shin0x.space/api/templates/$TEMPLATE_ID/render | jq -r '.jobId')

# Poll + download (same pattern as above)

Example 4: Multi-Block Template (Video + Audio + Text + Subtitle)

For a template with multiple blocks — fill each one in a single request.

{
"blocks": {
"main-video": {
"upload_url": "https://peako.shin0x.space/assets/video-uuid.mp4"
},
"background-music": {
"upload_url": "https://peako.shin0x.space/assets/music-uuid.mp3"
},
"title-text": {
"rich_text": {
"content": "Top 5 Tips for 2026",
"style": {
"fontFamily": "Inter",
"fontSize": 56,
"color": "#FFFFFF",
"bold": true,
"align": "center"
},
"transform": {
"x": 0.05,
"y": 0.08,
"width": 0.90,
"height": 0.12
}
}
},
"subtitle-track": {
"auto_transcribe": true,
"transcribe_from": "main-video"
}
},
"outputFormat": "mp4",
"delivery": "async"
}

Common Errors & Troubleshooting

ErrorHTTPCauseFix
BLOCK_NOT_FOUND400You filled a block that doesn't exist in the templateCheck blockSchema — use exact blockId values from the template
REQUIRED_BLOCK_MISSING400A required: true block was not filled in your requestFill all blocks marked required: true in blockSchema
BLOCK_TYPE_MISMATCH400You tried to fill a block with the wrong type (e.g., audio fill on video block)Match fill type to block type (see Block Fill Types table)
CDN_URL_NOT_ALLOWED400src_url is from an external URL, not the Peako CDNUpload the file first via /api/assets/upload, then use the returned URL
TRANSCRIBE_SOURCE_MISSING400transcribe_from references a block that isn't filledEnsure the referenced block has a src_url or upload_url fill
TRANSCRIBE_SOURCE_INVALID400transcribe_from references a non-video/audio blockOnly video and audio blocks can be transcribed
QUEUE_DEPTH_EXCEEDED409User has 20+ active jobsWait for pending jobs to complete before submitting new ones
VALIDATION_FAILED400Request body is malformed or missing required fieldsCheck the request structure against examples above

Polling After Render

After submitting a render, poll GET /api/jobs/:jobId to track progress.

See Jobs for the full reference.

Quick polling pattern:

while true; do
RESULT=$(curl -s -H "X-API-Key: $API_KEY" \
https://peako.shin0x.space/api/jobs/$JOB_ID)
STATUS=$(echo "$RESULT" | jq -r '.status')

[ "$STATUS" = "done" ] && echo $(echo "$RESULT" | jq -r '.outputUrl') && break
[ "$STATUS" = "failed" ] && echo "Error: $(echo "$RESULT" | jq -r '.error')" && exit 1

sleep 5
done

Recommended polling intervals:

Content LengthPoll Interval
< 30 secondsEvery 2–5 seconds
30 seconds – 5 minutesEvery 10 seconds
> 5 minutesEvery 30 seconds

Next: Subtitles & Transcription