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
- You have a template with defined blocks (e.g., one video block, one subtitle block)
- At render time, you fill each block with content
- The render worker combines everything and outputs a video
- 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.
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 Type | Allowed Fills |
|---|---|
video | src_url, upload_url |
image | src_url, upload_url |
audio | src_url, upload_url |
text | text, rich_text |
subtitle | text, captions, auto_transcribe |
Duration Modes
Each block has a durationMode that controls how the block duration is calculated:
| Mode | Behavior | Use Case |
|---|---|---|
locked | Block uses its template-defined duration. Asset is trimmed or padded to fit. | Fixed-length sections (intro, outro, fixed video slots) |
trim_to_asset | Block duration stretches to match the actual asset length. Useful for variable-length content. | Content with unpredictable length |
stretch_to_slot | Asset 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:
| Field | Type | Required | Description |
|---|---|---|---|
blocks | object | ✓ | Map of blockId → BlockFill. Keys must match block IDs in the template. |
outputFormat | string | ✗ | "mp4" (default), "webm", or "mov" |
delivery | string | ✗ | "async" (default), "storage", or "response" |
canvasSize | object | ✗ | Override canvas size: { width, height } in pixels |
Delivery Modes:
| Mode | Behavior | Best For |
|---|---|---|
async | Returns { jobId, status: "queued" } immediately. Poll GET /api/jobs/:jobId. | Default. Recommended for any non-trivial render. |
storage | Renders and uploads result to CDN. Returns job ID and eventual outputUrl. | When you want the result stored on the CDN. |
response | Streams 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 successfully400— Invalid block fill, missing required block, validation failed401— Missing or invalid API key404— Template not found409— 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"
}
| Field | Type | Required | Description |
|---|---|---|---|
src_url | string | ✓ | CDN 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"
}
| Field | Type | Required | Description |
|---|---|---|---|
upload_url | string | ✓ | The 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!"
}
| Field | Type | Required | Description |
|---|---|---|---|
text | string | ✓ | Plain 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:
| Field | Type | Required | Description |
|---|---|---|---|
rich_text.content | string | ✓ | Text to display |
rich_text.style | object | ✗ | Style options (see table below) |
rich_text.transform | object | ✗ | Override block position/size (0.0–1.0 normalized coords) |
Style Fields:
| Field | Type | Options | Description |
|---|---|---|---|
fontFamily | string | "Inter", "Arial", "Georgia", "Courier", etc. | Font name |
fontSize | number | pixels, e.g., 32 | Font size |
color | string | hex code, e.g., "#FFFFFF" | Text color |
bold | boolean | true / false | Bold text |
italic | boolean | true / false | Italic text |
align | string | "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:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique ID for this caption entry (e.g., "1", "2") |
from | number | ✓ | Start time in milliseconds from video start |
to | number | ✓ | End time in milliseconds from video start |
text | string | ✓ | Caption 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
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"
}
| Field | Type | Required | Description |
|---|---|---|---|
auto_transcribe | boolean | ✓ | Must be true |
transcribe_from | string | ✓ | blockId 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:
- The render worker downloads the source video/audio block's asset
- Extracts the audio track
- Runs faster-whisper (local speech-to-text) on the audio
- Generates a timed captions array from the transcription
- Injects the captions into the subtitle block
- Proceeds with normal rendering
Constraints:
transcribe_frommust reference avideooraudioblock in the template- That block must be filled with a
src_urlorupload_urlin the same render request - The render returns
202immediately; 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
| Scenario | Fill Type | Pros | Cons |
|---|---|---|---|
| Auto-generate from video audio — Don't have captions yet, want them automatic | auto_transcribe: true | Simplest, one-step process | Adds transcription time to render |
Already transcribed — You have captions from a previous /api/transcribe call or external tool | captions array | Reusable, no redundant transcription, precise control | Requires pre-transcription step |
| Have SRT file — You already have an .srt/.vtt/.ass file | Parse with /api/subtitles/parse, then use captions array | Works with any existing subtitle file | 2-step process |
| Static text — Simple static caption for entire block duration (no timing) | text fill | Simplest syntax | No 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:
| Format | Codec | Audio | Use Case |
|---|---|---|---|
mp4 (default) | H.264 | AAC | Maximum compatibility, streaming, web |
webm | VP8/VP9 | Vorbis | Web, open-source projects |
mov | ProRes | PCM | High-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
}
}
| Field | Type | Description |
|---|---|---|
width | number | Canvas width in pixels |
height | number | Canvas 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
| Error | HTTP | Cause | Fix |
|---|---|---|---|
BLOCK_NOT_FOUND | 400 | You filled a block that doesn't exist in the template | Check blockSchema — use exact blockId values from the template |
REQUIRED_BLOCK_MISSING | 400 | A required: true block was not filled in your request | Fill all blocks marked required: true in blockSchema |
BLOCK_TYPE_MISMATCH | 400 | You 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_ALLOWED | 400 | src_url is from an external URL, not the Peako CDN | Upload the file first via /api/assets/upload, then use the returned URL |
TRANSCRIBE_SOURCE_MISSING | 400 | transcribe_from references a block that isn't filled | Ensure the referenced block has a src_url or upload_url fill |
TRANSCRIBE_SOURCE_INVALID | 400 | transcribe_from references a non-video/audio block | Only video and audio blocks can be transcribed |
QUEUE_DEPTH_EXCEEDED | 409 | User has 20+ active jobs | Wait for pending jobs to complete before submitting new ones |
VALIDATION_FAILED | 400 | Request body is malformed or missing required fields | Check 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 Length | Poll Interval |
|---|---|
| < 30 seconds | Every 2–5 seconds |
| 30 seconds – 5 minutes | Every 10 seconds |
| > 5 minutes | Every 30 seconds |