Video Operations
Standalone FFmpeg-powered video processing endpoints. Each operation is stateless and synchronous — it processes the input file and immediately returns the output (or a presigned URL/job ID depending on delivery mode).
All routes require authentication via x-api-key header.
Delivery Modes
Every video operation supports three delivery modes. Choose based on your use case:
response
Binary video stream returned directly in the HTTP response body.
- Behavior: Synchronous. Request blocks until processing completes.
- Response headers:
Content-Type: video/mp4(or video/webm, video/quicktime),Content-Disposition: attachment; filename="UUID.ext" - Status code: 200
- When to use: Caller wants the processed video immediately. Best for small files (under 50 MB) and simple request-response flows.
- Cleanup: Output is stored in tmpfs and cleaned by the stale-dir watcher after the stream is consumed.
Example response:
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="a7f3c1e0.mp4"
Content-Length: 15728640
[binary video data...]
storage
Output uploaded to MinIO peako-output bucket. A presigned URL (24h TTL) returned in JSON.
- Behavior: Synchronous. Request blocks until processing completes AND the file is uploaded to MinIO.
- Response body:
{
"url": "https://peako-minio.shin0x.space/peako-output/peako-op-output-uuid.mp4?X-Amz-Algorithm=...",
"expires_at": "2026-03-20T15:30:00Z"
} - Status code: 200
- MinIO cleanup: Object is automatically deleted after the HTTP response is fully sent (fire-and-forget). Client must download within 24h — in practice, the URL becomes invalid once the response lifecycle ends.
- When to use: Caller wants a URL to pass to another step (e.g., n8n workflow chaining). The presigned URL hostname is
peako-minio.shin0x.spaceand can be passed directly asurl/urlsto another ops route without re-uploading.
Example response:
{
"url": "https://peako-minio.shin0x.space/peako-output/peako-op-output-uuid.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...",
"expires_at": "2026-03-20T15:30:00Z"
}
async
Job enqueued in BullMQ. Returns a job ID immediately. Caller polls GET /api/jobs/:id for status.
- Behavior: Non-blocking. Returns immediately with a job ID. Processing happens in a background worker queue.
- Response body:
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued",
"promoted_to_async": true // only present if auto-promoted from response/storage due to file size
} - Status code: 202 (Accepted)
- Auto-promotion: Files >50 MB are automatically promoted to async regardless of requested delivery mode. Response includes
promoted_to_async: true. - Queue limit: Max 20 concurrent async jobs per user. Returns 429 if limit reached.
- When to use: Large files (>50 MB), or when caller does not need immediate response. Poll
GET /api/jobs/:idto check status and retrieve the output URL.
Example response:
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued"
}
Chaining Operations
Ops routes can be chained: pass the url from a storage delivery response as url/urls to another route.
How it works: Peako detects URLs from peako-minio.shin0x.space and routes them through the MinIO SDK client directly, bypassing DNS. This works from within Docker where peako-minio.shin0x.space does not resolve over the network.
Example chain:
1. POST /api/trim (delivery: storage) → returns url
2. POST /api/blur (url: [url from step 1], delivery: storage) → returns new url
3. POST /api/merge (urls: [[url from step 2], other_url], delivery: response) → returns binary video
URL Parameter Security
All url and urls parameters are validated by both SSRF DNS check AND CDN hostname allowlist:
- SSRF Check: Verifies the domain resolves to a public IP (no private IPs, localhost, or link-local addresses)
- CDN Allowlist: Verifies the hostname is in the allowlisted CDN providers (*.b-cdn.net, *.bunnycdn.com, *.shin0x.space, *.r2.dev, peako-minio.shin0x.space, etc.)
Both checks must pass. URLs must be publicly resolvable AND come from trusted CDN providers.
POST /api/trim
Trim a video to a time range. Returns only the [start, start+duration] segment.
Authentication: Required (x-api-key header)
Request — Model A (Multipart)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@input.mp4" \
-F "start=5" \
-F "duration=10" \
-F "output_format=mp4" \
-F "delivery=response" \
https://peako.shin0x.space/api/trim
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
start | number | ✅ | — | Start time in seconds. Must be ≥ 0. |
duration | number | ✅ | — | Duration in seconds. Must be > 0. Clamped to remaining video length. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"start": 5,
"duration": 10,
"output_format": "mp4",
"delivery": "response"
}' \
https://peako.shin0x.space/api/trim
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
start | number | ✅ | — | Start time in seconds. Must be ≥ 0. |
duration | number | ✅ | — | Duration in seconds. Must be > 0. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Response Examples
delivery: response (200 OK)
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="a7f3c1e0.mp4"
Content-Length: 5242880
[binary video data]
delivery: storage (200 OK)
{
"url": "https://peako-minio.shin0x.space/peako-output/peako-op-output-uuid.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
"expires_at": "2026-03-20T15:30:00Z"
}
delivery: async (202 Accepted)
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "queued"
}
POST /api/merge
Concatenate 2–10 video clips into a single file. Optional fade transition between clips.
Authentication: Required (x-api-key header)
Request — Model A (Multipart)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@intro.mp4" \
-F "file=@main.mp4" \
-F "file=@outro.mp4" \
-F "transition=fade" \
-F "transition_duration=0.5" \
-F "output_format=mp4" \
-F "delivery=response" \
https://peako.shin0x.space/api/merge
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file[0..9] | binary | ✅ (min 2) | — | Video files. Any field name accepted. Min 2, max 10. Each max 2 GB. Iterated in upload order. |
transition | string | ❌ | none | none, fade |
transition_duration | number | ❌ | 0.5 | Transition duration in seconds. Only applies when transition != none. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/intro.mp4",
"https://example.com/main.mp4",
"https://example.com/outro.mp4"
],
"transition": "fade",
"transition_duration": 0.5,
"output_format": "mp4",
"delivery": "response"
}' \
https://peako.shin0x.space/api/merge
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
urls | array | ✅ | — | Array of video URLs. Min 2, max 10. Each URL must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
transition | string | ❌ | none | none, fade |
transition_duration | number | ❌ | 0.5 | Transition duration in seconds. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Edge Case Example: MinIO URL Chaining
# Trim video → storage (get URL) → Merge that URL with another video
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://peako-minio.shin0x.space/peako-output/peako-op-output-uuid.mp4?X-Amz-Algorithm=...",
"https://example.com/outro.mp4"
],
"transition": "fade",
"delivery": "response"
}' \
https://peako.shin0x.space/api/merge
The first URL from a previous trim operation is passed directly as urls[0]. Peako detects the peako-minio.shin0x.space hostname and routes it through MinIO SDK.
Response Examples
delivery: response (200 OK)
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="merged-video.mp4"
Content-Length: 52428800
[binary video data]
delivery: storage (200 OK)
{
"url": "https://peako-minio.shin0x.space/peako-output/merged-uuid.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&...",
"expires_at": "2026-03-20T16:00:00Z"
}
POST /api/blur
Apply blur to a video — either full-frame or a specific rectangular region.
Authentication: Required (x-api-key header)
Request — Model A (Multipart)
# Full-frame blur
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@input.mp4" \
-F "intensity=20" \
-F "output_format=mp4" \
-F "delivery=response" \
https://peako.shin0x.space/api/blur
# Regional blur (face, license plate, etc.)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@input.mp4" \
-F "intensity=20" \
-F "region={\"x\":100,\"y\":50,\"width\":200,\"height\":100}" \
-F "delivery=response" \
https://peako.shin0x.space/api/blur
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
intensity | number | ❌ | 15 | Blur strength. Min 1, max 100. Maps to FFmpeg boxblur radius. |
region | JSON | ❌ | — | Blur region (JSON string in multipart): {"x": 100, "y": 50, "width": 200, "height": 100}. All values in pixels. Omit for full-frame blur. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
# Full-frame blur
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"intensity": 20,
"delivery": "response"
}' \
https://peako.shin0x.space/api/blur
# Regional blur
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"intensity": 20,
"region": {
"x": 100,
"y": 50,
"width": 200,
"height": 100
},
"delivery": "response"
}' \
https://peako.shin0x.space/api/blur
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
intensity | number | ❌ | 15 | Blur strength. Min 1, max 100. |
region | object | ❌ | — | Blur region: { x: number, y: number, width: number, height: number }. All values in pixels, non-negative. Omit for full-frame. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
POST /api/mute
Remove audio track from a video. Output is video-only, no audio.
Authentication: Required (x-api-key header)
Request — Model A (Multipart)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@input.mp4" \
-F "output_format=mp4" \
-F "delivery=response" \
https://peako.shin0x.space/api/mute
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"delivery": "response"
}' \
https://peako.shin0x.space/api/mute
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Response Example
delivery: response (200 OK)
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="muted-video.mp4"
Content-Length: 15728640
[binary video data — audio track removed]
POST /api/add-audio
Mix or replace audio track on a video. Supports two modes: mix (blend original + new audio) or replace (strip original, use new audio only).
Authentication: Required (x-api-key header)
Request — Model A (Multipart)
# Mix mode: blend video audio + new audio
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@video.mp4" \
-F "audio=@music.mp3" \
-F "mode=mix" \
-F "audio_volume=0.8" \
-F "video_volume=0.5" \
-F "delivery=response" \
https://peako.shin0x.space/api/add-audio
# Replace mode: use new audio only
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@video.mp4" \
-F "audio=@voiceover.mp3" \
-F "mode=replace" \
-F "delivery=response" \
https://peako.shin0x.space/api/add-audio
# Audio from URL
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@video.mp4" \
-F "audio_url=https://example.com/music.mp3" \
-F "mode=mix" \
-F "delivery=response" \
https://peako.shin0x.space/api/add-audio
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
audio | binary | ✅ (or audio_url) | — | Audio file (mp3, aac, wav, ogg, flac). Field name must be exactly audio. |
audio_url | string | ✅ (or audio) | — | URL of audio file as a form text field. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
mode | string | ❌ | mix | mix (blend audio), replace (strip original) |
audio_volume | number | ❌ | 1.0 | Volume multiplier for new audio. Min 0, max 2. (1.0 = normal, 0.5 = half, 2.0 = double) |
video_volume | number | ❌ | 1.0 | Volume multiplier for original video audio. Only applies in mix mode. Min 0, max 2. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"audio_url": "https://example.com/music.mp3",
"mode": "mix",
"audio_volume": 0.8,
"video_volume": 0.5,
"delivery": "response"
}' \
https://peako.shin0x.space/api/add-audio
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. |
audio_url | string | ✅ | — | Audio file URL. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
mode | string | ❌ | mix | mix, replace |
audio_volume | number | ❌ | 1.0 | Volume multiplier for new audio. Min 0, max 2. |
video_volume | number | ❌ | 1.0 | Volume multiplier for original video audio (mix mode only). Min 0, max 2. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
POST /api/speed-ramp
Apply variable playback speed to a video using keypoints. Each keypoint defines the playback speed at a specific time.
Authentication: Required (x-api-key header)
Important: The speed-ramp route hardcodes output to mp4. No output_format parameter is accepted or needed. This is a known inconsistency with other ops routes.
Request — Model A (Multipart)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@input.mp4" \
-F "keypoints=[{\"timeSec\":0,\"speed\":1.0},{\"timeSec\":5,\"speed\":2.0},{\"timeSec\":10,\"speed\":1.0}]" \
-F "delivery=response" \
https://peako.shin0x.space/api/speed-ramp
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
keypoints | JSON | ✅ | — | JSON array: [{timeSec: number, speed: number}]. Sorted by timeSec ascending. Min 1 keypoint. Speed range: 0.25–4.0. |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"keypoints": [
{ "timeSec": 0, "speed": 0.5 },
{ "timeSec": 5, "speed": 1.0 },
{ "timeSec": 10, "speed": 2.0 }
],
"delivery": "response"
}' \
https://peako.shin0x.space/api/speed-ramp
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. Accepts peako-minio.shin0x.space presigned URLs. |
keypoints | array | ✅ | — | Array of { timeSec: number, speed: number }. Min 1 keypoint. Sorted by timeSec. Speed: 0.25–4.0. |
delivery | string | ❌ | response | response, storage, async |
Keypoint Format
| Field | Type | Notes |
|---|---|---|
timeSec | number | Position in the source video (seconds). |
speed | number | Playback speed multiplier. 0.25 = quarter speed (slow-mo), 1.0 = normal, 2.0 = double speed (fast-forward). Range: 0.25–4.0. |
FFmpeg uses piecewise linear interpolation between keypoints. Audio pitch is adjusted to match speed changes.
Response Example
delivery: response (200 OK)
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="speed-ramped.mp4"
Content-Length: 8388608
[binary video data — variable speed applied]
POST /api/subtitle
Burn subtitle text permanently into video frames (hard-coded captions). Accepts SRT or ASS format.
Authentication: Required (x-api-key header)
The subtitle_url parameter only accepts URLs from peako.shin0x.space/assets/*. This is intentional security design. External subtitle hosting is not supported. Use the /api/transcribe endpoint to generate subtitle SRT files stored at peako.shin0x.space/assets/*.
Request — Model A (Multipart)
# Subtitle file upload
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@video.mp4" \
-F "subtitle=@captions.srt" \
-F "output_format=mp4" \
-F "delivery=response" \
https://peako.shin0x.space/api/subtitle
# Subtitle file upload — ASS format
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@video.mp4" \
-F "subtitle=@captions.ass" \
-F "format=ass" \
-F "delivery=response" \
https://peako.shin0x.space/api/subtitle
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
file | binary | ✅ | — | Video file. Max 2 GB. |
subtitle | binary | ✅ (or subtitle_url) | — | Subtitle file (.srt or .ass). Field name must be exactly subtitle. Format auto-detected from extension. |
subtitle_url | string | ✅ (or subtitle) | — | URL of subtitle file as a form text field. Must be peako.shin0x.space/assets/* URL only. |
format | string | ❌ | — | Override auto-detected format (srt or ass). Rarely needed. |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Request — Model B (JSON)
# Subtitle from peako.shin0x.space/assets/ (auto-transcribe output)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"subtitle_url": "https://peako.shin0x.space/assets/srt-uuid.srt",
"delivery": "response"
}' \
https://peako.shin0x.space/api/subtitle
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
url | string | ✅ | — | Video URL. Must pass SSRF DNS check AND CDN allowlist validation. |
subtitle_url | string | ✅ | — | Subtitle URL. Must be peako.shin0x.space/assets/* only. This is where auto-transcribe SRT files are stored. |
format | string | ❌ | — | Override auto-detected format (srt or ass). |
output_format | string | ❌ | mp4 | mp4, mov, webm |
delivery | string | ❌ | response | response, storage, async |
Subtitle File Formats
SRT (SubRip)
1
00:00:00,000 --> 00:00:02,500
First subtitle text
2
00:00:02,500 --> 00:00:05,000
Second subtitle text
ASS (Advanced SubStation Alpha)
[Script Info]
Title: My Subtitles
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,0,0,0,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.00,0:00:02.50,Default,,0,0,0,,First subtitle text
Dialogue: 0,0:00:02.50,0:00:05.00,Default,,0,0,0,,Second subtitle text
Response Example
delivery: response (200 OK)
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Disposition: attachment; filename="subtitled-video.mp4"
Content-Length: 20971520
[binary video data — subtitles burned into frames]
Error Codes Reference
All video operations return standard HTTP error codes with JSON error bodies:
{
"error": "ERROR_CODE",
"message": "Human-readable error message"
}
Common Errors (All Routes)
| Status | Code | Meaning | Occurs When |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Request body validation failed | Missing required field, wrong type, invalid value |
| 400 | INPUT_MISSING | Required input field missing | url/urls not provided |
| 400 | FILE_VALIDATION_ERROR | File upload failed | Wrong MIME type, corrupted file header, not a valid video/audio file |
| 400 | WRONG_FIELD_NAME | Field name error | Using singular url instead of urls for merge endpoint |
| 413 | PAYLOAD_TOO_LARGE | File too large | Exceeds 2 GB limit per file |
| 429 | RATE_LIMITED | Queue limit reached | Max 20 concurrent async jobs per user (async delivery mode only) |
| 502 | BAD_GATEWAY | Download failed | Failed to fetch video from url or audio from audio_url |
Route-Specific Errors
POST /api/trim | POST /api/blur | POST /api/mute | POST /api/add-audio | POST /api/speed-ramp | POST /api/subtitle
| Status | Code | Meaning | Example |
|---|---|---|---|
| 500 | RENDER_FAILED | FFmpeg operation failed | "Trim render failed: codec not supported" |
POST /api/merge
| Status | Code | Notes |
|---|---|---|
| 400 | MINIMUM_FILES_REQUIRED | At least 2 files required |
| 400 | MAXIMUM_FILES_EXCEEDED | Maximum 10 files for merge |
| 400 | USE_URLS_ARRAY | Singular url used instead of array urls |
POST /api/add-audio
| Status | Code | Notes |
|---|---|---|
| 400 | AUDIO_FIELD_MISSING | Neither audio file nor audio_url provided |
| 400 | AUDIO_VALIDATION_FAILED | Audio file validation failed (not mp3/aac/wav/ogg/flac) |
POST /api/subtitle
| Status | Code | Notes |
|---|---|---|
| 400 | SUBTITLE_FIELD_MISSING | Neither subtitle file nor subtitle_url provided |
| 400 | SUBTITLE_PARSE_FAILED | SRT/ASS file could not be parsed |
| 400 | SUBTITLE_NO_ENTRIES | Subtitle file contains no valid cue entries |
| 400 | SUBTITLE_URL_INVALID | subtitle_url is not a peako.shin0x.space/assets/* URL |
Edge Case Examples
Example 1: MinIO URL Chaining (storage → storage)
Trim a video and store it, then use that output as input to merge:
# Step 1: Trim → storage
TRIM_RESPONSE=$(curl -s -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/long-video.mp4",
"start": 0,
"duration": 30,
"delivery": "storage"
}' \
https://peako.shin0x.space/api/trim)
TRIMMED_URL=$(echo $TRIM_RESPONSE | jq -r '.url')
# Step 2: Merge that URL with another video
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"urls\": [
\"$TRIMMED_URL\",
\"https://example.com/outro.mp4\"
],
\"delivery\": \"response\"
}" \
https://peako.shin0x.space/api/merge
Example 2: File Type Error (Wrong MIME Type)
# ❌ WRONG: Uploading a text file as video
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@notes.txt" \
https://peako.shin0x.space/api/trim
# Response: 400 Bad Request
# {
# "error": "FILE_VALIDATION_ERROR",
# "message": "File validation failed: expected video/mp4, got text/plain"
# }
Example 3: Missing Required Field
# ❌ WRONG: No start/duration for trim
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4"
}' \
https://peako.shin0x.space/api/trim
# Response: 400 Bad Request
# {
# "error": "VALIDATION_ERROR",
# "message": "Validation failed: start is required (required)"
# }
Example 4: URL Security Validation
# ❌ WRONG: Private IP address (SSRF blocked)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "http://192.168.1.100/video.mp4",
"delivery": "response"
}' \
https://peako.shin0x.space/api/trim
# Response: 400 Bad Request
# {
# "error": "VALIDATION_ERROR",
# "message": "URL fails SSRF check: private IP not allowed"
# }
# ❌ WRONG: Non-allowlisted CDN domain
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://random-cdn.com/video.mp4",
"delivery": "response"
}' \
https://peako.shin0x.space/api/trim
# Response: 400 Bad Request
# {
# "error": "VALIDATION_ERROR",
# "message": "URL fails CDN allowlist check: domain not in allowlist"
# }
Example 5: File Size Over Limit
# ❌ WRONG: 5 GB file (over 2 GB limit)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@huge-video.mp4" \
https://peako.shin0x.space/api/trim
# Response: 413 Payload Too Large
# {
# "error": "PAYLOAD_TOO_LARGE",
# "message": "File too large. Maximum upload size is 2 GB."
# }
Example 6: Auto-Promotion to Async (File >50 MB)
# POST with delivery: response but file is 100 MB
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@large-video.mp4" \
-F "start=0" \
-F "duration=10" \
-F "delivery=response" \
https://peako.shin0x.space/api/trim
# Response: 202 Accepted (auto-promoted to async)
# {
# "job_id": "550e8400-e29b-41d4-a716-446655440000",
# "status": "queued",
# "promoted_to_async": true
# }
Example 7: Merge Array Bounds
# ❌ WRONG: Only 1 file (min 2 required)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://example.com/video.mp4"],
"delivery": "response"
}' \
https://peako.shin0x.space/api/merge
# Response: 400 Bad Request
# {
# "error": "MINIMUM_FILES_REQUIRED",
# "message": "At least 2 files required for merge"
# }
# ❌ WRONG: 15 files (max 10)
curl -X POST \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/1.mp4",
"https://example.com/2.mp4",
... (13 more) ...
],
"delivery": "response"
}' \
https://peako.shin0x.space/api/merge
# Response: 400 Bad Request
# {
# "error": "MAXIMUM_FILES_EXCEEDED",
# "message": "Maximum 10 files for merge"
# }
Next: Effects & Transitions