From f017e6cb60afd9e52fa3a0894b4485d261e47ac5 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 07:09:44 +0300 Subject: [PATCH 01/11] docs: document ffmpeg multi-file output and input sources --- guides/ffmpeg.mdx | 115 ++++++++++++++++++++++++++++++++++++++++++- job-types/ffmpeg.mdx | 68 +++++++++++++++++++++++-- 2 files changed, 179 insertions(+), 4 deletions(-) diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index ae34477..b2b2241 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -52,12 +52,61 @@ From a shell? Use [the CLI](/cli/overview): `rb ffmpeg -i input.mp4 -vf scale=12 5. **Upload**: output goes to R2 6. **Return**: signed `outputUrl` available via `GET /jobs/{id}` +## Input sources + +The `inputs` map is the working directory for your command. Each key is a filename that gets staged under that exact name. Each value is the source for that file. Your command references the file by its bare name. + +A value can be one of four shapes: + +| Value | Use it for | +|---|---| +| `"https://..."` | A remote file. Shorthand for `{ "url": "..." }` | +| `{ "url": "https://..." }` | A remote file, explicit form | +| `{ "content": "...text..." }` | Inline text you don't want to host (≤64 KB) | +| `{ "ref": "uploads/..." }` | A file you already uploaded | + +```json +{ + "type": "ffmpeg", + "inputs": { + "clip.mp4": "https://example.com/clip.mp4", + "logo.png": { "ref": "uploads/brand-logo.png" } + }, + "params": { + "command": "ffmpeg -i clip.mp4 -i logo.png -filter_complex overlay=10:10 output.mp4" + } +} +``` + +Inline `-i https://...` URLs in the command still work, so `inputs` can stay `{}` for simple jobs. Use `inputs` when a file needs a stable name or has no public URL. + +### Auxiliary files read by a filter + +Some files aren't passed as `-i` arguments. Filters like `subtitles=`, `lut3d=`, and `drawtext fontfile=`, and the lists read by the `concat` demuxer, read a file by name from the working directory. Provide those through `inputs` so they're staged before the command runs. + +This burns a subtitle track that lives only in the request, with no file to host: + +```json +{ + "type": "ffmpeg", + "inputs": { + "video.mp4": "https://example.com/video.mp4", + "subs.srt": { + "content": "1\n00:00:01,000 --> 00:00:04,000\nRendered on Rendobar.\n" + } + }, + "params": { + "command": "ffmpeg -i video.mp4 -vf subtitles=subs.srt -c:v libx264 -c:a copy output.mp4" + } +} +``` + ## Parameters | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `type` | string | yes | - | `"ffmpeg"` | -| `inputs` | object | yes | - | `{}` when URLs are inline in the command. Otherwise a name→URL map matching filenames in the command | +| `inputs` | object | yes | - | Working directory for the command. Maps a filename to a source (URL, `{url}`, `{content}`, or `{ref}`). `{}` when every input is an inline `-i` URL. See [Input sources](#input-sources) | | `params.command` | string | yes | - | Real FFmpeg command starting with `ffmpeg`. Input URLs go in `-i` positions | | `params.outputFormat` | string | no | inferred | Override container format. Inferred from trailing filename if omitted | | `params.timeout` | int | no | `120` | Server-side max execution in seconds. Range 1–900 | @@ -161,6 +210,57 @@ Plan timeouts: 5 min (Free), 15 min (Pro). See [plan limits](/support/limits) fo } ``` +## Output files + +The filenames your command writes define the output. You get back every file it produced. + +A command that writes one file returns a single download URL. Fetch the job and read `outputUrl`: + +```json +{ "data": { "id": "job_abc123", "status": "complete", "outputUrl": "https://serve.rendobar.com/..." } } +``` + +A command that writes many files (HLS or DASH segments, image sequences, `-f segment`) returns an `output` object instead. It carries the full set: + +```json +{ + "type": "ffmpeg", + "inputs": { "video.mp4": "https://example.com/video.mp4" }, + "params": { + "command": "ffmpeg -i video.mp4 -c:v libx264 -c:a aac -f hls -hls_time 6 -hls_segment_filename seg_%03d.ts master.m3u8" + } +} +``` + +The completed job carries the playlist plus every segment: + +```json +{ + "data": { + "id": "job_abc123", + "status": "complete", + "output": { + "type": "stream", + "url": "https://serve.rendobar.com/t/abc/master.m3u8", + "playlist": "master.m3u8", + "baseUrl": "https://serve.rendobar.com/t/abc/", + "expiresAt": 1735689600000, + "fileCount": 5, + "files": [ + { "path": "master.m3u8", "url": "https://serve.rendobar.com/t/abc/master.m3u8", "size": 412 }, + { "path": "seg_000.ts", "url": "https://serve.rendobar.com/t/abc/seg_000.ts", "size": 1048576 }, + { "path": "seg_001.ts", "url": "https://serve.rendobar.com/t/abc/seg_001.ts", "size": 1041203 } + ], + "manifestUrl": "https://serve.rendobar.com/t/abc/_manifest.json" + } + } +} +``` + +For a stream, `output.url` is the playable playlist. Point a player at it directly. An unordered collection (image sequence, resolution ladder) has `output.type: "set"` and no top-level `url`. Read `files` or `manifestUrl` for the full list. + +Every URL expires at `output.expiresAt`. Re-fetch the job with `GET /jobs/{id}` to mint fresh ones. + ## Error handling FFmpeg exits non-zero → job fails with `PROVIDER_ERROR`: @@ -169,6 +269,19 @@ FFmpeg exits non-zero → job fails with `PROVIDER_ERROR`: { "error": { "code": "PROVIDER_ERROR", "message": "FFmpeg process exited with code 1" } } ``` +A failed job also carries `errorDetail`: the last ~2 KB of the real FFmpeg stderr. Fetch the job to read it and see exactly why FFmpeg stopped. + +```json +{ + "data": { + "id": "job_abc123", + "status": "failed", + "errorCode": "PROVIDER_ERROR", + "errorDetail": "[in#0 @ 0x55…] Error opening input: Invalid data found when processing input\nError opening input file clip.mp4.\n" + } +} +``` + Disallowed flag → rejected before dispatch with `VALIDATION_ERROR`: ```json diff --git a/job-types/ffmpeg.mdx b/job-types/ffmpeg.mdx index 67c4612..b364267 100644 --- a/job-types/ffmpeg.mdx +++ b/job-types/ffmpeg.mdx @@ -29,7 +29,7 @@ keywords: ["ffmpeg api reference", "ffmpeg cloud", "ffmpeg sandboxed", "ffmpeg r **Live.** Production-ready, available on all plans. -Execute a custom FFmpeg command with whitelisted flags. Input files go in `-i` positions and are downloaded into a sandbox before execution. +Execute a custom FFmpeg command with whitelisted flags. The `inputs` map stages source files into the working directory by name. The command references them by bare name, and inline `-i` URLs also work. **Timeout:** 900 s max (plan-capped) · **Accepts:** video, audio, image @@ -89,8 +89,29 @@ res = requests.post( ## Parameters + + Working directory for the command. Each key is a path-safe filename staged under that exact name. Each value is the source for that file. `{}` when every input is an inline `-i` URL. A value is one of: + + + + Remote file. Shorthand for `{ "url": "..." }`. + + + `{ "url": "https://..." }`. Remote file, explicit form. + + + `{ "content": "...text..." }`. Inline text staged verbatim, up to 64 KB. For subtitles, `concat` lists, and LUTs with nothing to host. + + + `{ "ref": "uploads/..." }`. A file you already uploaded. + + + + Filters that read a file by name (`subtitles=`, `lut3d=`, `drawtext fontfile=`, `concat` lists) must get that file through `inputs`. They are not `-i` arguments. See the [FFmpeg guide](/guides/ffmpeg#input-sources). + + - Real FFmpeg command starting with `ffmpeg`. Input URLs go in `-i` positions. + Real FFmpeg command starting with `ffmpeg`. Input URLs go in `-i` positions, or reference staged `inputs` files by name. @@ -107,7 +128,48 @@ res = requests.post( { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`, then read `outputUrl` (signed, 1-hour TTL). +Poll `GET /jobs/{id}` until `status: "complete"`. The filenames your command writes define the output. + + + Signed download URL for a single-file output. Present when the command wrote one file. + + + + Present when the command wrote multiple files (HLS/DASH, image sequences, `-f segment`). + + + + `stream` for an HLS/DASH playlist. `set` for an unordered collection. + + + Playable playlist for a `stream`. Absent for a `set`. + + + Entry playlist filename, e.g. `master.m3u8`. + + + Serving base URL for the prefix, trailing slash. + + + Unix ms when every URL here expires. Re-fetch the job to refresh. + + + Total files under the served prefix. + + + Each file as `{ path, url, size }`. + + + URL of the machine-readable `_manifest.json` listing every file. + + + + + + On a failed job, the last ~2 KB of the real FFmpeg stderr for debugging. + + +See [output files](/guides/ffmpeg#output-files) in the guide for full request and response examples. ## See also From 6a32bc8a3a584ad0e8021fdf5c68964915d3657d Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 07:52:49 +0300 Subject: [PATCH 02/11] docs: fix ffmpeg served output URLs to match API --- guides/ffmpeg.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index b2b2241..7e34307 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -217,7 +217,7 @@ The filenames your command writes define the output. You get back every file it A command that writes one file returns a single download URL. Fetch the job and read `outputUrl`: ```json -{ "data": { "id": "job_abc123", "status": "complete", "outputUrl": "https://serve.rendobar.com/..." } } +{ "data": { "id": "job_abc123", "status": "complete", "outputUrl": "https://api.rendobar.com/dl/job_abc123?token=" } } ``` A command that writes many files (HLS or DASH segments, image sequences, `-f segment`) returns an `output` object instead. It carries the full set: @@ -241,17 +241,17 @@ The completed job carries the playlist plus every segment: "status": "complete", "output": { "type": "stream", - "url": "https://serve.rendobar.com/t/abc/master.m3u8", + "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", "playlist": "master.m3u8", - "baseUrl": "https://serve.rendobar.com/t/abc/", + "baseUrl": "https://api.rendobar.com/v/job_abc123//", "expiresAt": 1735689600000, "fileCount": 5, "files": [ - { "path": "master.m3u8", "url": "https://serve.rendobar.com/t/abc/master.m3u8", "size": 412 }, - { "path": "seg_000.ts", "url": "https://serve.rendobar.com/t/abc/seg_000.ts", "size": 1048576 }, - { "path": "seg_001.ts", "url": "https://serve.rendobar.com/t/abc/seg_001.ts", "size": 1041203 } + { "path": "master.m3u8", "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", "size": 412 }, + { "path": "seg_000.ts", "url": "https://api.rendobar.com/v/job_abc123//seg_000.ts", "size": 1048576 }, + { "path": "seg_001.ts", "url": "https://api.rendobar.com/v/job_abc123//seg_001.ts", "size": 1041203 } ], - "manifestUrl": "https://serve.rendobar.com/t/abc/_manifest.json" + "manifestUrl": "https://api.rendobar.com/v/job_abc123//_manifest.json" } } } From 554ba146d34b81ada5f001b8c641f9587714f7f3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 08:26:59 +0300 Subject: [PATCH 03/11] docs: fix {ref} example to asset ID --- guides/ffmpeg.mdx | 4 ++-- job-types/ffmpeg.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index 7e34307..a1858f0 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -63,14 +63,14 @@ A value can be one of four shapes: | `"https://..."` | A remote file. Shorthand for `{ "url": "..." }` | | `{ "url": "https://..." }` | A remote file, explicit form | | `{ "content": "...text..." }` | Inline text you don't want to host (≤64 KB) | -| `{ "ref": "uploads/..." }` | A file you already uploaded | +| `{ "ref": "asset_abc123" }` | A file you already uploaded, by its asset ID (returned by the uploads endpoint) | ```json { "type": "ffmpeg", "inputs": { "clip.mp4": "https://example.com/clip.mp4", - "logo.png": { "ref": "uploads/brand-logo.png" } + "logo.png": { "ref": "asset_brandlogo" } }, "params": { "command": "ffmpeg -i clip.mp4 -i logo.png -filter_complex overlay=10:10 output.mp4" diff --git a/job-types/ffmpeg.mdx b/job-types/ffmpeg.mdx index b364267..9d11528 100644 --- a/job-types/ffmpeg.mdx +++ b/job-types/ffmpeg.mdx @@ -103,7 +103,7 @@ res = requests.post( `{ "content": "...text..." }`. Inline text staged verbatim, up to 64 KB. For subtitles, `concat` lists, and LUTs with nothing to host. - `{ "ref": "uploads/..." }`. A file you already uploaded. + `{ "ref": "asset_abc123" }`. A file you already uploaded, by its asset ID (returned by the uploads endpoint). From 06072afcab131897813131a4143d87755584703c Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 08:40:27 +0300 Subject: [PATCH 04/11] docs: lead ffmpeg inputs with inline URL, map as fallback --- guides/ffmpeg.mdx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index a1858f0..a8397eb 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -54,7 +54,19 @@ From a shell? Use [the CLI](/cli/overview): `rb ffmpeg -i input.mp4 -vf scale=12 ## Input sources -The `inputs` map is the working directory for your command. Each key is a filename that gets staged under that exact name. Each value is the source for that file. Your command references the file by its bare name. +Most commands put the source URL straight in the command and leave `inputs` empty: + +```json +{ + "type": "ffmpeg", + "inputs": {}, + "params": { + "command": "ffmpeg -i https://example.com/clip.mp4 -vf scale=1280:720 output.mp4" + } +} +``` + +Reach for the `inputs` map only when a file can't go inline: a file a filter reads by name (see below), inline text you don't want to host, an already-uploaded asset, or when you need a stable filename. Each key is a filename staged under that exact name, and the command references it by bare name. A value can be one of four shapes: @@ -78,8 +90,6 @@ A value can be one of four shapes: } ``` -Inline `-i https://...` URLs in the command still work, so `inputs` can stay `{}` for simple jobs. Use `inputs` when a file needs a stable name or has no public URL. - ### Auxiliary files read by a filter Some files aren't passed as `-i` arguments. Filters like `subtitles=`, `lut3d=`, and `drawtext fontfile=`, and the lists read by the `concat` demuxer, read a file by name from the working directory. Provide those through `inputs` so they're staged before the command runs. From a9ea5baef4cdd27f5f76c9abd158f271fdb7b85a Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 08:42:45 +0300 Subject: [PATCH 05/11] docs: mark ffmpeg inputs optional, omit in simple example --- guides/ffmpeg.mdx | 3 +-- job-types/ffmpeg.mdx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index a8397eb..4ede7e2 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -54,12 +54,11 @@ From a shell? Use [the CLI](/cli/overview): `rb ffmpeg -i input.mp4 -vf scale=12 ## Input sources -Most commands put the source URL straight in the command and leave `inputs` empty: +Most commands put the source URL straight in the command and skip `inputs` entirely. It's optional and defaults to an empty map: ```json { "type": "ffmpeg", - "inputs": {}, "params": { "command": "ffmpeg -i https://example.com/clip.mp4 -vf scale=1280:720 output.mp4" } diff --git a/job-types/ffmpeg.mdx b/job-types/ffmpeg.mdx index 9d11528..f3b7697 100644 --- a/job-types/ffmpeg.mdx +++ b/job-types/ffmpeg.mdx @@ -89,8 +89,8 @@ res = requests.post( ## Parameters - - Working directory for the command. Each key is a path-safe filename staged under that exact name. Each value is the source for that file. `{}` when every input is an inline `-i` URL. A value is one of: + + Working directory for the command. Optional, defaults to an empty map. Each key is a path-safe filename staged under that exact name. Each value is the source for that file. Omit it when every input is an inline `-i` URL. A value is one of: From 49849044cf0ee29e8f5b426d5af3b6e27a77970f Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 10:27:08 +0300 Subject: [PATCH 06/11] docs: update job result to discriminated output + error --- cli/ci-cd.mdx | 2 +- guides/ffmpeg.mdx | 50 ++++++++++++++-------- job-types/caption-burn.mdx | 2 +- job-types/captions-animate.mdx | 2 +- job-types/ffmpeg.mdx | 78 ++++++++++++++++++++++++++++------ mcp/examples.mdx | 2 +- mcp/tools.mdx | 6 +-- quickstart.mdx | 12 ++++-- snippets/poll-status.mdx | 2 +- support/limits.mdx | 4 +- 10 files changed, 116 insertions(+), 44 deletions(-) diff --git a/cli/ci-cd.mdx b/cli/ci-cd.mdx index 20922d5..449e5ed 100644 --- a/cli/ci-cd.mdx +++ b/cli/ci-cd.mdx @@ -122,7 +122,7 @@ Progress display goes to stderr. stdout stays empty unless you ask for it. | Goal | Flag | stdout | |---|---|---| | Result URL only | `--url-only` | One line, the signed download URL | -| Full result | `--json` | JSON: `id`, `status`, `outputUrl`, `cost`, timing | +| Full result | `--json` | JSON: `id`, `status`, `output`, `cost`, timing | | Exit code only | `--quiet` | (nothing) | | Submit and exit | `--no-wait` | One line, the job ID | diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index 4ede7e2..e8af673 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -50,7 +50,7 @@ From a shell? Use [the CLI](/cli/overview): `rb ffmpeg -i input.mp4 -vf scale=12 3. **Substitute**: URLs replaced with local paths in the FFmpeg command 4. **Execute**: FFmpeg runs in an isolated container with network disabled 5. **Upload**: output goes to R2 -6. **Return**: signed `outputUrl` available via `GET /jobs/{id}` +6. **Return**: signed `output.url` available via `GET /jobs/{id}` ## Input sources @@ -223,13 +223,31 @@ Plan timeouts: 5 min (Free), 15 min (Pro). See [plan limits](/support/limits) fo The filenames your command writes define the output. You get back every file it produced. -A command that writes one file returns a single download URL. Fetch the job and read `outputUrl`: +A completed job carries an `output` object discriminated on `kind`. A command that writes one file returns `output.kind: "file"` with the signed `url` and probed `meta`: ```json -{ "data": { "id": "job_abc123", "status": "complete", "outputUrl": "https://api.rendobar.com/dl/job_abc123?token=" } } +{ + "data": { + "id": "job_abc123", + "status": "complete", + "output": { + "kind": "file", + "url": "https://api.rendobar.com/dl/job_abc123?token=", + "expiresAt": 1735689600000, + "poster": null, + "meta": { + "format": "mp4", + "width": 1280, + "height": 720, + "durationMs": 30000, + "sizeBytes": 4194304 + } + } + } +} ``` -A command that writes many files (HLS or DASH segments, image sequences, `-f segment`) returns an `output` object instead. It carries the full set: +A command that writes many files (HLS or DASH segments, image sequences, `-f segment`) returns `output.kind: "stream"` or `output.kind: "set"` instead. It carries the full set: ```json { @@ -249,9 +267,9 @@ The completed job carries the playlist plus every segment: "id": "job_abc123", "status": "complete", "output": { - "type": "stream", + "kind": "stream", "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", - "playlist": "master.m3u8", + "manifest": "hls", "baseUrl": "https://api.rendobar.com/v/job_abc123//", "expiresAt": 1735689600000, "fileCount": 5, @@ -266,32 +284,30 @@ The completed job carries the playlist plus every segment: } ``` -For a stream, `output.url` is the playable playlist. Point a player at it directly. An unordered collection (image sequence, resolution ladder) has `output.type: "set"` and no top-level `url`. Read `files` or `manifestUrl` for the full list. +For a stream, `output.url` is the playable manifest. Point a player at it directly. An unordered collection (image sequence, resolution ladder) has `output.kind: "set"` and no top-level `url`. Read `files` or `manifestUrl` for the full list. Every URL expires at `output.expiresAt`. Re-fetch the job with `GET /jobs/{id}` to mint fresh ones. ## Error handling -FFmpeg exits non-zero → job fails with `PROVIDER_ERROR`: - -```json -{ "error": { "code": "PROVIDER_ERROR", "message": "FFmpeg process exited with code 1" } } -``` - -A failed job also carries `errorDetail`: the last ~2 KB of the real FFmpeg stderr. Fetch the job to read it and see exactly why FFmpeg stopped. +A failed job carries an `error` object with `code`, `message`, `detail`, and `retryable`. When FFmpeg exits non-zero, `error.detail` holds the last ~2 KB of the real stderr. Fetch the job to see exactly why FFmpeg stopped. ```json { "data": { "id": "job_abc123", "status": "failed", - "errorCode": "PROVIDER_ERROR", - "errorDetail": "[in#0 @ 0x55…] Error opening input: Invalid data found when processing input\nError opening input file clip.mp4.\n" + "error": { + "code": "PROVIDER_ERROR", + "message": "FFmpeg process exited with code 1", + "detail": "[in#0 @ 0x55…] Error opening input: Invalid data found when processing input\nError opening input file clip.mp4.\n", + "retryable": false + } } } ``` -Disallowed flag → rejected before dispatch with `VALIDATION_ERROR`: +A disallowed flag is rejected before dispatch. The request fails with the error envelope, so no job is created: ```json { "error": { "code": "VALIDATION_ERROR", "message": "Flag '-protocol_whitelist' is not allowed in FFmpeg commands" } } diff --git a/job-types/caption-burn.mdx b/job-types/caption-burn.mdx index 42bead7..6aaaba4 100644 --- a/job-types/caption-burn.mdx +++ b/job-types/caption-burn.mdx @@ -159,7 +159,7 @@ Omit `subtitles` to auto-transcribe. The spoken language is detected by default { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`, then read `outputUrl` (signed, 1-hour TTL). +Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.url` (signed, 1-hour TTL). The result is `output.kind: "file"`. ## See also diff --git a/job-types/captions-animate.mdx b/job-types/captions-animate.mdx index 725279e..adc5909 100644 --- a/job-types/captions-animate.mdx +++ b/job-types/captions-animate.mdx @@ -118,7 +118,7 @@ res = requests.post( { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`, then read `outputUrl` (signed, 1-hour TTL). +Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.url` (signed, 1-hour TTL). The result is `output.kind: "file"`. ## Pricing diff --git a/job-types/ffmpeg.mdx b/job-types/ffmpeg.mdx index f3b7697..a6c5136 100644 --- a/job-types/ffmpeg.mdx +++ b/job-types/ffmpeg.mdx @@ -128,24 +128,59 @@ res = requests.post( { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`. The filenames your command writes define the output. - - - Signed download URL for a single-file output. Present when the command wrote one file. - +Poll `GET /jobs/{id}` until `status: "complete"`. On success the job carries an `output` object discriminated on `kind`. On failure it carries an `error` object. The filenames your command writes define the output. - Present when the command wrote multiple files (HLS/DASH, image sequences, `-f segment`). + Present when `status` is `complete`. The `kind` field tells you the shape. + + + + A single-file result. The command wrote one file. + + + Signed download URL for the file. + + + Unix ms when `url` expires. Re-fetch the job to refresh. + + + Signed URL for a generated poster frame, or `null`. + + + Probed metadata: `format`, `width`, `height`, `durationMs`, `sizeBytes`. Each field is present when known. + + - - - `stream` for an HLS/DASH playlist. `set` for an unordered collection. + + + An HLS or DASH result. The command wrote a manifest plus segments. - Playable playlist for a `stream`. Absent for a `set`. + Playable manifest URL. Point a player at it directly. - - Entry playlist filename, e.g. `master.m3u8`. + + Which manifest format this stream uses. + + + Serving base URL for the prefix, trailing slash. + + + Unix ms when every URL here expires. Re-fetch the job to refresh. + + + Total files under the served prefix. + + + Each file as `{ path, url, size }`. + + + URL of the machine-readable `_manifest.json` listing every file. + + + + + + An unordered collection: image sequences, `-f segment` output, a resolution ladder. No top-level playable URL. Serving base URL for the prefix, trailing slash. @@ -165,8 +200,23 @@ Poll `GET /jobs/{id}` until `status: "complete"`. The filenames your command wri - - On a failed job, the last ~2 KB of the real FFmpeg stderr for debugging. + + Present when `status` is `failed`. + + + + Stable error code, e.g. `PROVIDER_ERROR`. + + + Human-readable summary of what went wrong. + + + Extra context. For FFmpeg failures, the last ~2 KB of stderr. `null` when there is nothing to add. + + + Whether re-running the same job could succeed. + + See [output files](/guides/ffmpeg#output-files) in the guide for full request and response examples. diff --git a/mcp/examples.mdx b/mcp/examples.mdx index e04ff69..09c6759 100644 --- a/mcp/examples.mdx +++ b/mcp/examples.mdx @@ -81,7 +81,7 @@ The agent polls `get_job` every few seconds. A 30-second clip with subtitles tak ## Step 5: Download -When `get_job` returns `status: "complete"`, it includes an `outputUrl`. The agent shows you a clickable link. Open it in your browser and the captioned video downloads. +When `get_job` returns `status: "complete"`, it includes `output.url`. The agent shows you a clickable link. Open it in your browser and the captioned video downloads. ## What just happened diff --git a/mcp/tools.mdx b/mcp/tools.mdx index 2c21f79..a3388e5 100644 --- a/mcp/tools.mdx +++ b/mcp/tools.mdx @@ -104,8 +104,8 @@ Poll a job by ID. - `waiting` / `dispatched`: `{ id, type, status }` - `running`: adds `progress` (0–1) and `step` (current step name) -- `complete`: adds `cost` (e.g. `"$0.05"`), `durationMs`, `outputUrl`, `output: { format, resolution, durationMs, fileSizeBytes }` -- `failed`: adds `error: { code, message }` +- `complete`: adds `cost` and `output`. For a single file, `output: { kind: "file", url, expiresAt, poster, meta: { format, width, height, durationMs, sizeBytes } }`. HLS/DASH returns `kind: "stream"`, image sequences return `kind: "set"` +- `failed`: adds `error: { code, message, detail, retryable }` - `cancelled`: terminal, no extra fields ## `list_jobs` @@ -124,7 +124,7 @@ Recent jobs. 1 to 50. -**Returns:** `{ jobs: [{ id, type, status, createdAt, cost, outputUrl? }], total }`. `outputUrl` is included only when `status` is `complete`. +**Returns:** `{ jobs: [{ id, type, status, createdAt, cost, output? }], total }`. `output` is included only when `status` is `complete`. Read `output.url` for the file. ## `cancel_job` diff --git a/quickstart.mdx b/quickstart.mdx index 3c8d941..54e9aec 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -108,15 +108,21 @@ For push instead of poll, configure a [webhook](/guides/webhooks). ## 4. Download the result -When `status` is `complete`, the response contains `outputUrl`, a signed R2 URL valid for one hour: +When `status` is `complete`, the response contains an `output` object. For a single file it's `output.kind: "file"` with a signed `url` valid for one hour: ```json { "data": { "id": "job_abc123", "status": "complete", - "outputUrl": "https://r2.rendobar.com/...", - "cost": { "nanodollars": 50000000, "formatted": "$0.05" } + "output": { + "kind": "file", + "url": "https://r2.rendobar.com/...", + "expiresAt": 1735689600000, + "poster": null, + "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000, "sizeBytes": 4194304 } + }, + "cost": { "amount": 50000000, "currency": "USD", "formatted": "$0.05" } } } ``` diff --git a/snippets/poll-status.mdx b/snippets/poll-status.mdx index 10dfd9c..b71edab 100644 --- a/snippets/poll-status.mdx +++ b/snippets/poll-status.mdx @@ -1,4 +1,4 @@ -Poll `GET /jobs/{id}` until `status` is `complete`. The response then contains `outputUrl` (signed, 1-hour expiry) and `cost.formatted`. +Poll `GET /jobs/{id}` until `status` is `complete`. The response then contains `output.url` (signed, 1-hour expiry) and `cost.formatted`. diff --git a/support/limits.mdx b/support/limits.mdx index a1fcb4d..b6a59e5 100644 --- a/support/limits.mdx +++ b/support/limits.mdx @@ -47,8 +47,8 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo ## Output URLs -- **Signed download URL** (`outputUrl`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. -- **Output retention**: 7 days (Free) or 30 days (Pro). After that, R2 deletes the file; the job record stays but `outputUrl` returns 404. +- **Signed download URL** (`output.url`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. +- **Output retention**: 7 days (Free) or 30 days (Pro). After that, R2 deletes the file; the job record stays but `output.url` returns 404. - **Storage quota** caps total bytes across all your outputs. Delete old outputs or upgrade if you hit it. - **No separate per-output size limit**. Bounded by input cap and storage quota. From 68c0903cad163735c97592d3e6ef3bf1b43156a8 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 14:02:11 +0300 Subject: [PATCH 07/11] docs: unify job output to data + file + files --- concepts/job-lifecycle.mdx | 48 ++++++++++++++++++ guides/ffmpeg.mdx | 90 ++++++++++++++++++++++++---------- job-types/caption-burn.mdx | 2 +- job-types/captions-animate.mdx | 2 +- job-types/ffmpeg.mdx | 77 +++++++++-------------------- mcp/examples.mdx | 2 +- mcp/tools.mdx | 2 +- quickstart.mdx | 27 +++++++--- snippets/poll-status.mdx | 2 +- support/limits.mdx | 4 +- 10 files changed, 161 insertions(+), 95 deletions(-) diff --git a/concepts/job-lifecycle.mdx b/concepts/job-lifecycle.mdx index e550d98..f56b1dd 100644 --- a/concepts/job-lifecycle.mdx +++ b/concepts/job-lifecycle.mdx @@ -62,6 +62,54 @@ waiting → dispatched → running → complete Full catalogue: [Error codes](/support/errors). +## Output shape + +A `complete` job carries one `output` shape, the same for every job type: + +```json +{ + "data": | null, + "file": | null, + "files": [], + "expiresAt": | null +} +``` + +- `output.data`: the computed answer (a probe result, detections, a transcript). `null` for jobs that only write files. +- `output.file`: the headline result to play or download, a single file or a stream manifest (`type: "playlist"`). `null` for data-only jobs and pure file sets. +- `output.files`: every file the job produced, the full list. `[]` for data-only jobs. +- `output.expiresAt`: Unix ms when the file URLs expire, present when `files` is non-empty. + +Each file is `{ url, path, type, size, meta? }`. The `type` is an open enum (`video`, `image`, `audio`, `captions`, `playlist`, `data`, `other`). Tolerate values you don't recognise. + +Two invariants hold. `output.file` is always one of `output.files` (or `null`). `output.expiresAt` is present whenever `output.files` is non-empty. + +A job that computes an answer rather than writing files returns it in `output.data`. The `data` field is job-type-specific, so each job type documents its own shape. An `extract.metadata` probe sets `data` and leaves `file` null and `files` empty: + +```json +{ + "data": { + "id": "job_abc123", + "status": "complete", + "output": { + "data": { + "format": "mp4", + "durationMs": 30000, + "streams": [ + { "type": "video", "codec": "h264", "width": 1280, "height": 720, "fps": 30 }, + { "type": "audio", "codec": "aac", "channels": 2, "sampleRate": 48000 } + ] + }, + "file": null, + "files": [], + "expiresAt": null + } + } +} +``` + +Read [How it runs](/guides/ffmpeg#output-files) for the file-producing variants (single file, stream, set). + ## Timeouts Each job type has a max execution time. Exceeding it auto-fails with `PROVIDER_TIMEOUT`; credits are refunded. A background job runs every 2 minutes to detect stuck jobs that were dispatched or running but never reported back. diff --git a/guides/ffmpeg.mdx b/guides/ffmpeg.mdx index e8af673..c68af8b 100644 --- a/guides/ffmpeg.mdx +++ b/guides/ffmpeg.mdx @@ -50,7 +50,7 @@ From a shell? Use [the CLI](/cli/overview): `rb ffmpeg -i input.mp4 -vf scale=12 3. **Substitute**: URLs replaced with local paths in the FFmpeg command 4. **Execute**: FFmpeg runs in an isolated container with network disabled 5. **Upload**: output goes to R2 -6. **Return**: signed `output.url` available via `GET /jobs/{id}` +6. **Return**: signed `output.file.url` available via `GET /jobs/{id}` ## Input sources @@ -223,7 +223,16 @@ Plan timeouts: 5 min (Free), 15 min (Pro). See [plan limits](/support/limits) fo The filenames your command writes define the output. You get back every file it produced. -A completed job carries an `output` object discriminated on `kind`. A command that writes one file returns `output.kind: "file"` with the signed `url` and probed `meta`: +Every completed job, FFmpeg or otherwise, returns the same `output` shape: + +- `output.data`: the computed answer (a probe result, a transcript). `null` for jobs that only write files. +- `output.file`: the headline result you play or download, one file or a stream manifest. `null` for data-only jobs and pure file sets. +- `output.files`: every file the job produced, the complete list. `[]` for data-only jobs. +- `output.expiresAt`: Unix ms when the URLs expire, present when `files` is non-empty. + +Each file is `{ url, path, type, size, meta? }`. The `type` is an open enum: `video`, `image`, `audio`, `captions`, `playlist`, `data`, or `other`. Tolerate values you don't recognise. + +A command that writes one file sets `output.file` to that file, and `output.files` to a list of one: ```json { @@ -231,23 +240,30 @@ A completed job carries an `output` object discriminated on `kind`. A command th "id": "job_abc123", "status": "complete", "output": { - "kind": "file", - "url": "https://api.rendobar.com/dl/job_abc123?token=", - "expiresAt": 1735689600000, - "poster": null, - "meta": { - "format": "mp4", - "width": 1280, - "height": 720, - "durationMs": 30000, - "sizeBytes": 4194304 - } + "data": null, + "file": { + "url": "https://api.rendobar.com/dl/job_abc123?token=", + "path": "output.mp4", + "type": "video", + "size": 4194304, + "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 } + }, + "files": [ + { + "url": "https://api.rendobar.com/dl/job_abc123?token=", + "path": "output.mp4", + "type": "video", + "size": 4194304, + "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 } + } + ], + "expiresAt": 1735689600000 } } } ``` -A command that writes many files (HLS or DASH segments, image sequences, `-f segment`) returns `output.kind: "stream"` or `output.kind: "set"` instead. It carries the full set: +A command that writes a stream (HLS or DASH) sets `output.file` to the manifest, typed `playlist`. Point a player at `output.file.url` directly. `output.files` carries the manifest plus every segment: ```json { @@ -259,7 +275,31 @@ A command that writes many files (HLS or DASH segments, image sequences, `-f seg } ``` -The completed job carries the playlist plus every segment: +```json +{ + "data": { + "id": "job_abc123", + "status": "complete", + "output": { + "data": null, + "file": { + "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", + "path": "master.m3u8", + "type": "playlist", + "size": 412 + }, + "files": [ + { "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", "path": "master.m3u8", "type": "playlist", "size": 412 }, + { "url": "https://api.rendobar.com/v/job_abc123//seg_000.ts", "path": "seg_000.ts", "type": "video", "size": 1048576 }, + { "url": "https://api.rendobar.com/v/job_abc123//seg_001.ts", "path": "seg_001.ts", "type": "video", "size": 1041203 } + ], + "expiresAt": 1735689600000 + } + } +} +``` + +A command that writes an unordered set (image sequence, `-f segment`, a resolution ladder) has no single headline result. `output.file` is `null`. Read `output.files` for the list: ```json { @@ -267,26 +307,22 @@ The completed job carries the playlist plus every segment: "id": "job_abc123", "status": "complete", "output": { - "kind": "stream", - "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", - "manifest": "hls", - "baseUrl": "https://api.rendobar.com/v/job_abc123//", - "expiresAt": 1735689600000, - "fileCount": 5, + "data": null, + "file": null, "files": [ - { "path": "master.m3u8", "url": "https://api.rendobar.com/v/job_abc123//master.m3u8", "size": 412 }, - { "path": "seg_000.ts", "url": "https://api.rendobar.com/v/job_abc123//seg_000.ts", "size": 1048576 }, - { "path": "seg_001.ts", "url": "https://api.rendobar.com/v/job_abc123//seg_001.ts", "size": 1041203 } + { "url": "https://api.rendobar.com/v/job_abc123//frame_000.png", "path": "frame_000.png", "type": "image", "size": 204800 }, + { "url": "https://api.rendobar.com/v/job_abc123//frame_001.png", "path": "frame_001.png", "type": "image", "size": 205120 }, + { "url": "https://api.rendobar.com/v/job_abc123//frame_002.png", "path": "frame_002.png", "type": "image", "size": 203904 } ], - "manifestUrl": "https://api.rendobar.com/v/job_abc123//_manifest.json" + "expiresAt": 1735689600000 } } } ``` -For a stream, `output.url` is the playable manifest. Point a player at it directly. An unordered collection (image sequence, resolution ladder) has `output.kind: "set"` and no top-level `url`. Read `files` or `manifestUrl` for the full list. +Consuming the result is the same every time. The answer is `output.data`. The thing you play or download is `output.file.url`. The full list is `output.files`. Two invariants hold: `output.file` is always one of `output.files` (or `null`), and `output.expiresAt` is present whenever `output.files` is non-empty. -Every URL expires at `output.expiresAt`. Re-fetch the job with `GET /jobs/{id}` to mint fresh ones. +URLs expire at `output.expiresAt`. Re-fetch the job with `GET /jobs/{id}` to mint fresh ones. ## Error handling diff --git a/job-types/caption-burn.mdx b/job-types/caption-burn.mdx index 6aaaba4..eca6847 100644 --- a/job-types/caption-burn.mdx +++ b/job-types/caption-burn.mdx @@ -159,7 +159,7 @@ Omit `subtitles` to auto-transcribe. The spoken language is detected by default { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.url` (signed, 1-hour TTL). The result is `output.kind: "file"`. +Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.file.url` (signed, 1-hour TTL). The burned video is a single file, so `output.files` lists that one file. ## See also diff --git a/job-types/captions-animate.mdx b/job-types/captions-animate.mdx index adc5909..d8b9bd5 100644 --- a/job-types/captions-animate.mdx +++ b/job-types/captions-animate.mdx @@ -118,7 +118,7 @@ res = requests.post( { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.url` (signed, 1-hour TTL). The result is `output.kind: "file"`. +Poll `GET /jobs/{id}` until `status: "complete"`, then read `output.file.url` (signed, 1-hour TTL). The captioned video is a single file, so `output.files` lists that one file. ## Pricing diff --git a/job-types/ffmpeg.mdx b/job-types/ffmpeg.mdx index a6c5136..a3d7602 100644 --- a/job-types/ffmpeg.mdx +++ b/job-types/ffmpeg.mdx @@ -128,74 +128,43 @@ res = requests.post( { "data": { "id": "job_abc123", "status": "dispatched" } } ``` -Poll `GET /jobs/{id}` until `status: "complete"`. On success the job carries an `output` object discriminated on `kind`. On failure it carries an `error` object. The filenames your command writes define the output. +Poll `GET /jobs/{id}` until `status: "complete"`. On success the job carries an `output` object. On failure it carries an `error` object. The filenames your command writes define the output. + +`output` is one shape for every job type. Read `output.data` for a computed answer, `output.file.url` for the file to play or download, and `output.files` for the full list. - Present when `status` is `complete`. The `kind` field tells you the shape. + Present when `status` is `complete`. - - - A single-file result. The command wrote one file. - - - Signed download URL for the file. + + + The computed answer for jobs that return one, e.g. a probe result. `null` for FFmpeg jobs that only write files. - - Unix ms when `url` expires. Re-fetch the job to refresh. + + The headline result: the single output file, or a stream manifest (`type: "playlist"`). `null` for a set of files with no single headline. Always one of `files`. - - Signed URL for a generated poster frame, or `null`. + + Every file the job produced. A single-file job lists one. A stream lists the manifest plus segments. A set lists every member. - - Probed metadata: `format`, `width`, `height`, `durationMs`, `sizeBytes`. Each field is present when known. + + Unix ms when the file URLs expire. Present when `files` is non-empty. Re-fetch the job to refresh. - - - An HLS or DASH result. The command wrote a manifest plus segments. - + - Playable manifest URL. Point a player at it directly. - - - Which manifest format this stream uses. - - - Serving base URL for the prefix, trailing slash. - - - Unix ms when every URL here expires. Re-fetch the job to refresh. + Signed download or serving URL. - - Total files under the served prefix. + + The filename your command wrote, e.g. `output.mp4`. - - Each file as `{ path, url, size }`. + + Open enum: `video`, `image`, `audio`, `captions`, `playlist`, `data`, or `other`. Tolerate unknown values. - - URL of the machine-readable `_manifest.json` listing every file. + + File size in bytes. - - - - - An unordered collection: image sequences, `-f segment` output, a resolution ladder. No top-level playable URL. - - - Serving base URL for the prefix, trailing slash. - - - Unix ms when every URL here expires. Re-fetch the job to refresh. - - - Total files under the served prefix. - - - Each file as `{ path, url, size }`. - - - URL of the machine-readable `_manifest.json` listing every file. + + Optional probed metadata: `format`, `width`, `height`, `durationMs`. Each field is present when known. diff --git a/mcp/examples.mdx b/mcp/examples.mdx index 09c6759..781a25b 100644 --- a/mcp/examples.mdx +++ b/mcp/examples.mdx @@ -81,7 +81,7 @@ The agent polls `get_job` every few seconds. A 30-second clip with subtitles tak ## Step 5: Download -When `get_job` returns `status: "complete"`, it includes `output.url`. The agent shows you a clickable link. Open it in your browser and the captioned video downloads. +When `get_job` returns `status: "complete"`, it includes `output.file.url`. The agent shows you a clickable link. Open it in your browser and the captioned video downloads. ## What just happened diff --git a/mcp/tools.mdx b/mcp/tools.mdx index a3388e5..a5499c4 100644 --- a/mcp/tools.mdx +++ b/mcp/tools.mdx @@ -124,7 +124,7 @@ Recent jobs. 1 to 50. -**Returns:** `{ jobs: [{ id, type, status, createdAt, cost, output? }], total }`. `output` is included only when `status` is `complete`. Read `output.url` for the file. +**Returns:** `{ jobs: [{ id, type, status, createdAt, cost, output? }], total }`. `output` is included only when `status` is `complete`. Read `output.file.url` for the file, or `output.data` for a computed answer. ## `cancel_job` diff --git a/quickstart.mdx b/quickstart.mdx index 54e9aec..ee1e4c9 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -108,7 +108,7 @@ For push instead of poll, configure a [webhook](/guides/webhooks). ## 4. Download the result -When `status` is `complete`, the response contains an `output` object. For a single file it's `output.kind: "file"` with a signed `url` valid for one hour: +When `status` is `complete`, the response contains an `output` object. The file to download is `output.file.url`, signed and valid for one hour: ```json { @@ -116,18 +116,31 @@ When `status` is `complete`, the response contains an `output` object. For a sin "id": "job_abc123", "status": "complete", "output": { - "kind": "file", - "url": "https://r2.rendobar.com/...", - "expiresAt": 1735689600000, - "poster": null, - "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000, "sizeBytes": 4194304 } + "data": null, + "file": { + "url": "https://r2.rendobar.com/...", + "path": "output.mp4", + "type": "video", + "size": 4194304, + "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 } + }, + "files": [ + { + "url": "https://r2.rendobar.com/...", + "path": "output.mp4", + "type": "video", + "size": 4194304, + "meta": { "format": "mp4", "width": 1280, "height": 720, "durationMs": 30000 } + } + ], + "expiresAt": 1735689600000 }, "cost": { "amount": 50000000, "currency": "USD", "formatted": "$0.05" } } } ``` -Re-fetch the job to refresh the URL after it expires. +Every job type returns this same `output` shape: `data` for a computed answer, `file` for the headline result, `files` for the full list. Re-fetch the job to refresh the URLs after they expire. ## What's next diff --git a/snippets/poll-status.mdx b/snippets/poll-status.mdx index b71edab..fee0b42 100644 --- a/snippets/poll-status.mdx +++ b/snippets/poll-status.mdx @@ -1,4 +1,4 @@ -Poll `GET /jobs/{id}` until `status` is `complete`. The response then contains `output.url` (signed, 1-hour expiry) and `cost.formatted`. +Poll `GET /jobs/{id}` until `status` is `complete`. The response then contains `output.file.url` (signed, 1-hour expiry) and `cost.formatted`. diff --git a/support/limits.mdx b/support/limits.mdx index b6a59e5..a4fc3ce 100644 --- a/support/limits.mdx +++ b/support/limits.mdx @@ -47,8 +47,8 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo ## Output URLs -- **Signed download URL** (`output.url`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. -- **Output retention**: 7 days (Free) or 30 days (Pro). After that, R2 deletes the file; the job record stays but `output.url` returns 404. +- **Signed download URL** (`output.file.url`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. +- **Output retention**: 7 days (Free) or 30 days (Pro). After that, R2 deletes the file; the job record stays but the file URLs return 404. - **Storage quota** caps total bytes across all your outputs. Delete old outputs or upgrade if you hit it. - **No separate per-output size limit**. Bounded by input cap and storage quota. From df0734cc27d251b61dc9f03111d1e262c4cbbb4e Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 6 Jun 2026 14:45:08 +0300 Subject: [PATCH 08/11] docs: canonical job output reference + per-type data --- concepts/job-lifecycle.mdx | 37 +---- concepts/job-output.mdx | 278 +++++++++++++++++++++++++++++++++++++ docs.json | 3 +- guides/ffmpeg.mdx | 8 +- job-types/ffmpeg.mdx | 42 +----- 5 files changed, 289 insertions(+), 79 deletions(-) create mode 100644 concepts/job-output.mdx diff --git a/concepts/job-lifecycle.mdx b/concepts/job-lifecycle.mdx index f56b1dd..2bb99f8 100644 --- a/concepts/job-lifecycle.mdx +++ b/concepts/job-lifecycle.mdx @@ -62,7 +62,7 @@ waiting → dispatched → running → complete Full catalogue: [Error codes](/support/errors). -## Output shape +## Output A `complete` job carries one `output` shape, the same for every job type: @@ -75,40 +75,7 @@ A `complete` job carries one `output` shape, the same for every job type: } ``` -- `output.data`: the computed answer (a probe result, detections, a transcript). `null` for jobs that only write files. -- `output.file`: the headline result to play or download, a single file or a stream manifest (`type: "playlist"`). `null` for data-only jobs and pure file sets. -- `output.files`: every file the job produced, the full list. `[]` for data-only jobs. -- `output.expiresAt`: Unix ms when the file URLs expire, present when `files` is non-empty. - -Each file is `{ url, path, type, size, meta? }`. The `type` is an open enum (`video`, `image`, `audio`, `captions`, `playlist`, `data`, `other`). Tolerate values you don't recognise. - -Two invariants hold. `output.file` is always one of `output.files` (or `null`). `output.expiresAt` is present whenever `output.files` is non-empty. - -A job that computes an answer rather than writing files returns it in `output.data`. The `data` field is job-type-specific, so each job type documents its own shape. An `extract.metadata` probe sets `data` and leaves `file` null and `files` empty: - -```json -{ - "data": { - "id": "job_abc123", - "status": "complete", - "output": { - "data": { - "format": "mp4", - "durationMs": 30000, - "streams": [ - { "type": "video", "codec": "h264", "width": 1280, "height": 720, "fps": 30 }, - { "type": "audio", "codec": "aac", "channels": 2, "sampleRate": 48000 } - ] - }, - "file": null, - "files": [], - "expiresAt": null - } - } -} -``` - -Read [How it runs](/guides/ffmpeg#output-files) for the file-producing variants (single file, stream, set). +Read `output.data` for a computed answer, `output.file.url` for the file to play or download, and `output.files` for the full list. The shape, the `File` type, the invariants, and the per-type `data` shapes live in one place: [Job output](/concepts/job-output). ## Timeouts diff --git a/concepts/job-output.mdx b/concepts/job-output.mdx new file mode 100644 index 0000000..0e740f6 --- /dev/null +++ b/concepts/job-output.mdx @@ -0,0 +1,278 @@ +--- +title: "Job output shape" +description: "Read the output shape every Rendobar job returns: data, file, files, and expiresAt, the File type, and the four output patterns with examples." +sidebarTitle: "Job output" +icon: "box-open" +keywords: ["job output", "output shape", "output.data", "output.file", "output.files", "file type enum", "rendobar response"] +--- + +