Ship your first media job in minutes.
Give MediaRuntime a fetchable media URL, request exactly the outputs you need, and receive a signed terminal webhook. Use the optional upload endpoint only when you do not already have a source URL.
Create, wait, then download the ZIP bundle.
The CLI accepts relative or absolute local file paths and uploads the bytes automatically. MediaRuntime also accepts a public HTTP(S) URL or a time-limited signed read URL directly. For your first run, use the CLI's --download option or an SDK's job.wait() helper to receive the canonical ZIP bundle. In production, persist job_id and consume the signed account webhook instead of polling.
# Submit a public or time-limited HTTPS source directly.
# Keep the URL fetchable until MediaRuntime has downloaded the input.
curl -sS -X POST "https://mediaruntime.com/v1/jobs" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://cdn.example.com/media/launch-trailer.mp4",
"metadata": { "asset_id": "asset_0426", "media_type": "video" },
"outputs": ["video.web"]
}'Clone a complete quickstart
Runnable Node.js and Python SDK projects, Go and PHP HTTP examples, signed webhook receivers, and Postman guidance live together in one public repository.
# Optional: use this when you have local bytes but no fetchable source URL.
UPLOAD=$(curl -sS -X POST "https://mediaruntime.com/v1/upload-url" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"launch-trailer.mp4","content_type":"video/mp4"}')
UPLOAD_URL=$(printf '%s' "$UPLOAD" | jq -r .upload_url)
FILE_URI=$(printf '%s' "$UPLOAD" | jq -r .file_uri)
UPLOAD_CONTENT_TYPE=$(printf '%s' "$UPLOAD" | jq -r '.upload_headers["Content-Type"]')
UPLOAD_AUTH=$(printf '%s' "$UPLOAD" | jq -r '.upload_headers.Authorization // empty')
UPLOAD_ARGS=(-H "Content-Type: $UPLOAD_CONTENT_TYPE")
if [[ -n "$UPLOAD_AUTH" ]]; then
UPLOAD_ARGS+=(-H "Authorization: $UPLOAD_AUTH")
fi
curl -sS -X PUT "$UPLOAD_URL" "${UPLOAD_ARGS[@]}" \
--upload-file ./launch-trailer.mp4
# Then use "$FILE_URI" as source in POST /v1/jobs.{
"job_id": "job_1320c28b72104811b075a26a99496cf6",
"status": "QUEUED",
"tier": "standard",
"required_tier": "standard",
"outputs": [{
"alias": "video.web",
"type": "mp4",
"preset": "mp4_720p_h264_aac"
}],
"msg": "accepted"
}Run and inspect media jobs from your terminal.
The official CLI is the fastest path for local files, production diagnostics, bundle downloads, and testing a local webhook receiver. The CLI, Node SDK, and Python SDK are stable 1.x packages whose documented interfaces follow semantic versioning. They use the same public job contract and never change the ZIP bundle model.
npm install --global @mediaruntime/cli
mediaruntime login
mediaruntime jobs list --limit 3Submit a local file and download the complete bundle
Pass a relative path such as ./launch.mp4 or an absolute local file path; the CLI uploads it automatically before creating the job. In an interactive terminal, a spinner shows the upload, waiting, and verified download phases. --download waits for a terminal result and publishes the canonical ZIP atomically. Existing files are preserved unless --force is explicit.
# The CLI uploads this local file before creating the job.
# Relative paths such as ./launch.mp4 work too.
mediaruntime run "/Users/you/Videos/launch.mp4" \
-o video.streaming \
-o audio.transcription \
--metadata '{"asset_id":"launch-01"}' \
--idempotency-key 'asset:launch-01:v1' \
--download ./launch-01.zipDiscover the live public catalog
mediaruntime capabilities summarizes aliases and features, while mediaruntime presets list returns the ordered public preset catalog. These read-only commands require neither browser login nor MEDIARUNTIME_API_KEY.
# Public discovery commands do not require login or an API key.
mediaruntime capabilities
mediaruntime presets list
# Use JSON when another tool will consume the catalog.
mediaruntime presets list --jsonRun exact public presets
Use repeatable --preset for exact catalog entries such as DASH or VP9. The CLI validates every name against the live public catalog before creating the job; aliases and exact presets can be combined in request order.
# Preset names are validated against the live public catalog.
mediaruntime run ./launch.mp4 \
--preset dash_ladder_v1 \
--preset webm_vp9_1080p \
--download ./adaptive-and-vp9.zipInspect and retrieve jobs
List one page of account jobs, filter by status, inspect one job, or download its retained ZIP bundle. Use the opaque cursor printed by jobs list to request the next page.
mediaruntime jobs list --status COMPLETED --limit 20
mediaruntime jobs get job_123
mediaruntime jobs get job_123 --download ./job_123.zipUse API keys for automation
CI, servers, and containers should inject MEDIARUNTIME_API_KEY from a secret manager. Do not place credentials in command arguments, source control, logs, or plaintext configuration files.
# Permanently supported for CI, servers, and containers.
export MEDIARUNTIME_API_KEY="sk_..."
mediaruntime jobs list --limit 3| Capability | Command contract | Notes |
|---|---|---|
| Output aliases | --output video.web | All six frozen aliases are accepted; repeat --output for multiple deliverables. |
| Machine output | --json | Writes one compact URL-redacted JSON result for scripts and CI. |
| Safe retries | --idempotency-key | Reuse one business key for the same logical job across process restarts. |
| Bundle safety | --download / --force | Downloads only terminal bundles, verifies integrity when advertised, and refuses accidental overwrite. |
| Exit status | 0–9, 130 | Authentication, API rejection, terminal failure, timeout, trigger, and bundle errors have distinct nonzero codes. |
export MEDIARUNTIME_WEBHOOK_SECRET="whsec_..."
mediaruntime trigger job.completed \
--to http://127.0.0.1:3000/webhooks/mediaruntimeKeep API keys on your server.
Create a key from Account → API Keys. The raw key is shown once and belongs in your secret manager, never in browser code, mobile binaries, logs, or source control.
Header
Send X-API-Key on every /v1 request.
Storage
Store MEDIARUNTIME_API_KEY in a server-side secret manager.
Rotation
Create a replacement, deploy it, verify traffic, then revoke the old key.
npm install --global @mediaruntime/cli
mediaruntime login
mediaruntime jobs list --limit 3Make metadata do the integration work.
The production pattern used by wMedia is intentionally simple: submit an input, output recipes, and enough opaque metadata to reconnect the terminal event to your own database record.
{
"source": "https://cdn.example.com/media/source.mp4",
"metadata": {
"producer": "my-api",
"entity_id": "video_01J8Y4",
"owner_id": "user_482",
"media_type": "video",
"trace_id": "req_f839"
},
"moderation": {
"enabled": true,
"mode": "report",
"checks": ["sexual", "violence", "dangerous"]
},
"watermark": { "enabled": true },
"outputs": [
{
"type": "mp4",
"preset": "mp4_720p_h264_aac",
"path_suffix": "web",
"poster_time_sec": 2,
"gif_preview": {
"enabled": true,
"width": 320,
"fps": 8,
"start_time": 2,
"duration": 2.5
}
},
{
"type": "image",
"preset": "image_multi_v1",
"path_suffix": "covers",
"images": [
{ "width": 1280, "height": 720, "mode": "fit", "format": "webp", "quality": 84 },
{ "width": 480, "height": 480, "mode": "cover", "format": "webp", "quality": 80 }
]
}
]
}| Field | Type | Notes |
|---|---|---|
| source | string or object | Canonical single input: a public HTTP(S), time-limited signed HTTP(S), accessible gs:// URL, or an object containing only url. |
| file_url | string | Permanent legacy spelling of scalar source. Do not combine source and file_url. |
| inputs | array | Batch fan-out for 1–25 inputs. Each item uses canonical source; legacy file_url remains supported per item. Do not combine inputs with either single-input field. |
| outputs | array | 1–10 output recipes. Each requires type; preset is strongly recommended. |
| metadata | object | Up to 32 KiB of JSON. Persisted and echoed at meta.request_metadata. |
| moderation | object | Premium visual-media checks: sexual, violence, dangerous. |
| watermark | object | Premium visual-media overlay. The account must already have a PNG logo. |
Batch fan-out
Use a batch when every input needs the same outputs. Per-input metadata is merged into each child job; the parent job becomes your batch reference.
{
"inputs": [
{
"source": "https://cdn.example.com/a.mp4",
"input_id": "asset-a",
"metadata": { "position": 0 }
},
{
"source": "https://cdn.example.com/b.mp4",
"input_id": "asset-b",
"metadata": { "position": 1 }
}
],
"metadata": { "batch_id": "import_2026_08_09" },
"outputs": [{ "type": "mp4", "preset": "mp4_720p_h264_aac" }]
}Safe retries with Idempotency-Key
A timed-out request is ambiguous: the job may have been queued and only the response lost. Send an Idempotency-Key header and retrying is safe — the same key returns the original job instead of queueing and charging for a second one. Without the header, behaviour is unchanged and every POST creates a new job.
# One key per logical job. Generate it in your client, not inside the retry loop.
KEY=$(uuidgen) # or a deterministic id you can regenerate: asset_0426:mp4_720p:v1
curl -sS -X POST "https://mediaruntime.com/v1/jobs" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY" \
-H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://example.com/source.mp4",
"metadata": { "asset_id": "asset_0426" },
"outputs": [{ "type": "mp4", "preset": "mp4_720p_h264_aac" }]
}'
# Timed out? Send the exact same request again with the SAME key.
# You get the original job_id back - no second job, no second charge.Key rules
- A UUID works; a deterministic id like
asset_0426:mp4_720p:v1is better, because you can regenerate it after a crash. - Keys are scoped to your account and honoured for 24 hours.
- Reusing a key with a different body returns 422 — usually one key reused across a loop.
- A retry sent while the first is still running returns 409; retry after a short backoff.
- Submitting the same file twice on purpose? Use two different keys.
Choose the artifact you want the engine to produce.
Start with a frozen output alias for common jobs. Use an explicit type and preset when you need to customize the recipe; mismatched pairs can be rejected or routed incorrectly.
Frozen output aliases
Aliases are stable gateway contracts. They resolve before validation, estimation, billing, and persistence, and can be mixed with explicit output objects in the same outputs array.
| Alias | Resolves to | Artifacts | Tier |
|---|---|---|---|
| video.web | mp4 / mp4_720p_h264_aac, JPG poster at 2s | 720p H.264/AAC MP4 and a JPG poster | Standard |
| video.streaming | hls / hls_ladder_v1 | HLS master, 1080p/720p variants, and segments | Standard |
| video.social | social / social_vertical_blur | 1080×1920 MP4 with a blurred 9:16 fill | Premium |
| audio.web | audio / audio_aac_128k | 128 kbps AAC/M4A | Standard |
| audio.transcription | audio / audio_aac_128k with base subtitles | AAC/M4A plus SRT and WebVTT transcripts | Standard |
| image.web | image / image_multi_v1 with two WebP renditions | 1200×630 and 320×320 WebP renditions | Premium |
{
"source": "https://cdn.example.com/landscape-interview.mp4",
"outputs": ["video.social"]
}social_vertical_blur creates a 1080×1920 H.264/AAC MP4. The source is scaled to fit without cropping; a blurred copy fills the 9:16 canvas behind it. Use it for Reels, TikTok, and Shorts. It is a Premium recipe because the vertical output is 1920 pixels high.Video files and posters
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| mp4_720p_h264_aac (type: mp4) | Video | Web-ready 720p H.264/AAC MP4 with fast start. MP4 video. | Standard |
| mp4_ladder_v1 (type: mp4) | Video | H.264/AAC MP4 renditions at 1080p, 720p, and 480p. 1080p MP4, 720p MP4, 480p MP4. | Standard |
| transmux_mp4_fast (type: mp4) | Video | Copies compatible streams into fast-start MP4, with an optional encoded fallback. MP4 video. | Standard |
| poster_frame_v1 (type: mp4) | Video | Extracts one poster frame at the requested timestamp. JPG poster. | Standard |
| mp4_hevc_1080p (type: mp4) | Video | 1080p HEVC/AAC MP4 with the Apple-compatible hvc1 tag. HEVC MP4. | Premium |
| mp4_av1_smart (type: mp4) | Video | 1080p AV1/Opus output for compression-efficient delivery. AV1 MP4. | Premium |
| mov_prores_422 (type: mp4) | Video | ProRes 422 HQ/PCM MOV editing master. ProRes MOV. | Premium |
Social video
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| audiogram_v1 (type: social) | Audio or video with audio | Composes timed audio, fitted artwork, a high-contrast waveform, and optional supplied captions in a reserved safe band into a social-ready video. H.264/AAC audiogram MP4, caption-free poster JPEG, audiogram.json, audiogram.waveform.json. | Premium |
| social_vertical_blur (type: social) | Video | Creates a 1080x1920 vertical H.264 video with a blurred fill background. vertical MP4. | Premium |
Animated GIF
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| gif_hq (type: gif) | Image or video | Creates a palette-optimized animated GIF. animated GIF. | Standard |
Frame extraction
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| contact_sheet_v1 (type: frames) | Video | Creates bounded composite review sheets with tile-to-source timestamp metadata. numbered contact-sheet images, contact_sheet.json. | Standard |
| extract_frames_1 (type: frames) | Video | Extracts a numbered JPG frame sequence at one frame per second. JPG frames at 1 fps. | Standard |
| extract_frames_5 (type: frames) | Video | Extracts a numbered JPG frame sequence at five frames per second. JPG frames at 5 fps. | Standard |
| scene_detect_v1 (type: frames) | Video | Detects shot boundaries and exports one keyframe per scene. scene JPGs, scene timeline. | Standard |
| perceptual_hash_v1 (type: frames) | Video | Samples a video and emits similarity-ready perceptual hashes. phash.json. | Standard |
Streaming
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| hls_ladder_v1 (type: hls) | Video | Encoded H.264/AAC HLS VOD ladder at 1080p and 720p. HLS master playlist, variant playlists, media segments. | Standard |
| transmux_hls_fast (type: hls) | Video | Packages compatible streams as HLS without re-encoding, with an optional encoded fallback. HLS master playlist, variant playlist, media segments. | Standard |
MPEG-DASH streaming
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| dash_ladder_v1 (type: dash) | Video | Encoded H.264/AAC MPEG-DASH ladder at 1080p and 720p. DASH MPD, initialization segments, media segments. | Standard |
| transmux_dash_fast (type: dash) | Video | Packages compatible streams as MPEG-DASH without re-encoding, with an optional encoded fallback. DASH MPD, initialization segments, media segments. | Standard |
WebM video
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| webm_vp9_1080p (type: webm) | Video | 1080p VP9/Opus WebM for modern browser delivery. VP9 WebM. | Premium |
Audio
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| audio_copy_fast (type: audio) | Audio or video with audio | Copies a compatible audio stream without re-encoding. audio file. | Standard |
| audio_aac_128k (type: audio) | Audio or video with audio | Encodes 128 kbps AAC in an M4A container. M4A audio. | Standard |
| audio_mp3_128k (type: audio) | Audio or video with audio | Encodes 128 kbps MP3. MP3 audio. | Standard |
| audio_opus_96k (type: audio) | Audio or video with audio | Encodes 96 kbps Opus. Opus audio. | Standard |
| audio_loudnorm_aac_128k (type: audio) | Audio or video with audio | Normalizes loudness to -16 LUFS and encodes 128 kbps AAC. normalized M4A audio, loudness metrics. | Standard |
| audio_trim_silence_aac_128k (type: audio) | Audio or video with audio | Trims leading and trailing silence and encodes 128 kbps AAC. trimmed M4A audio. | Premium |
| audio_loudnorm_trim_aac_128k (type: audio) | Audio or video with audio | Trims boundary silence, normalizes to -16 LUFS, and encodes 128 kbps AAC. trimmed and normalized M4A audio, loudness metrics. | Premium |
| audio_whisper_prep (type: audio) | Audio or video with audio | Creates 16 kHz mono PCM WAV for speech-recognition pipelines. WAV audio. | Standard |
Image derivatives
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| image_multi_v1 (type: image) | Image or video | Creates requested image renditions; optional JPG/WebP max_bytes constraints are verified against the final file, and content-aware smart crop is a Premium control. image renditions, image_size_limits.json when max_bytes is used, smart_crop.json when enabled. | Standard |
| image_animated_webp_v1 (type: image) | Video | Creates a bounded animated WebP clip with configurable size, frame rate, duration, and loop count. animated WebP. | Premium |
| image_animated_apng_v1 (type: image) | Video | Creates a bounded lossless animated PNG clip with configurable size, frame rate, duration, and loop count. animated PNG. | Premium |
| image_placeholders_v1 (type: image) | Image or video | Creates standards-compatible BlurHash and ThumbHash values, explicit source/placeholder geometry, an alpha-aware dominant colour, and a byte-bounded WebP loading placeholder. placeholders.json, lqip.webp. | Standard |
Analysis and reports
| Preset | Send | Job execution | Base tier |
|---|---|---|---|
| compatibility_report_v1 (type: image) | Video | Evaluates a video against versioned web, mobile, social-upload, and editing profiles with rule-level evidence and corrective preset recommendations. compatibility_report.json. | Standard |
| media_report_v1 (type: image) | Audio, image, or video | Inspects container, audio/video streams, GOP structure when present, and embedded metadata without transcoding. media_report.json. | Standard |
| code_detect_v1 (type: frames) | Image or video | Scans a bounded opening window for QR codes and barcodes. Audio is supported when it contains embedded cover artwork. codes.json, evidence frames when codes are found. | Standard |
transmux_*_fast presets avoid a quality-changing encode when source streams are compatible with MP4, HLS, or DASH. If copy fails and fallback is enabled, the engine uses the corresponding encoded preset. Use an encoded preset directly when you require predictable codec and rendition characteristics.Use one source. Produce the formats and sidecar artifacts your product needs.
The source extension does not select the output. The type and preset choose the executable recipe, so one uploaded video can become playback video, audio-only media, transcripts, GIF previews, posters, or frame sequences in the same asynchronous job.
| Source | Deliverable | Recipe |
|---|---|---|
| JPG, PNG, or WebP | JPG, PNG, WebP, or AVIF derivatives | image + image_multi_v1; choose images[].format, dimensions, mode, and quality. JPG/WebP can add max_bytes and min_quality for a verified hard ceiling. |
| Video | Web MP4, HLS, social video, or editing master | Choose the matching mp4, hls, or social preset. |
| Video | Animated GIF, poster, or JPG frame sequence | Use gif_hq, poster_frame_v1, extract_frames_1/5, or attach gif_preview to a video output. |
| Video or audio | M4A, MP3, Opus, or speech WAV | Choose the corresponding audio_* preset; video inputs have their audio stream extracted. |
| Video or audio speech | SRT, WebVTT, or both | Add subtitles to an audio or video output and choose srt, vtt, or both. |
{
"source": "https://cdn.example.com/media/product-photo.png",
"metadata": {
"media_type": "image",
"asset_id": "product-photo-0426"
},
"outputs": [{
"type": "image",
"preset": "image_multi_v1",
"path_suffix": "converted",
"images": [
{ "width": 1600, "height": 1200, "mode": "fit", "format": "jpg", "quality": 88 },
{ "width": 1600, "height": 1200, "mode": "fit", "format": "webp", "quality": 82 }
]
}]
}{
"source": "https://cdn.example.com/media/interview.mov",
"outputs": [
{ "type": "mp4", "preset": "mp4_720p_h264_aac", "path_suffix": "web-video" },
{ "type": "audio", "preset": "audio_mp3_128k", "path_suffix": "audio-only" }
]
}{
"source": "https://cdn.example.com/media/interview.mp4",
"metadata": { "media_type": "video", "asset_id": "interview-0426" },
"outputs": [{
"type": "audio",
"preset": "audio_aac_128k",
"path_suffix": "audio-and-transcript",
"subtitles": {
"enabled": true,
"languages": ["auto"],
"format": "both",
"model": "ggml-base.bin",
"translate_to_english": false
}
}]
}{
"source": "https://cdn.example.com/media/trailer.mp4",
"metadata": { "media_type": "video", "asset_id": "trailer-0426" },
"outputs": [{
"type": "mp4",
"preset": "mp4_720p_h264_aac",
"path_suffix": "web",
"poster_time_sec": 4,
"poster_format": "jpg",
"gif_preview": {
"enabled": true,
"width": 480,
"fps": 10,
"start_time": 4,
"duration": 3
}
}]
}{
"source": "https://cdn.example.com/media/feature-film.mp4",
"metadata": { "media_type": "video", "asset_id": "stream-0426" },
"outputs": [{
"type": "hls",
"preset": "hls_ladder_v1",
"path_suffix": "stream"
}]
}{
"source": "https://cdn.example.com/media/clip.mp4",
"metadata": { "media_type": "video", "asset_id": "clip-0426" },
"outputs": [
{ "type": "gif", "preset": "gif_hq", "path_suffix": "animated-preview" },
{ "type": "frames", "preset": "extract_frames_1", "path_suffix": "sampled-frames" }
]
}hls_ladder_v1 creates a master playlist, 1080p and 720p H.264/AAC variant playlists, and 6-second media segments. Use the reported master-playlist URL, or move the complete bundle together.gif_hq creates a full 480px, 15 fps primary GIF. gif_preview adds a shorter, explicitly timed GIF to another video output. Frame presets return numbered JPG sequences at one or five frames per second.Useful switches
audio_aac_128k returns M4A, audio_mp3_128k returns MP3, audio_opus_96k returns Opus, and audio_whisper_prep returns 16 kHz mono WAV. Set subtitles.format to srt, vtt, or both. For only one poster image, send type: mp4 with preset: poster_frame_v1 and the desired poster_time_sec.
Start with a preset; override only what matters.
Presets keep requests readable and give the engine a stable baseline. Add explicit rendition, subtitle, preview, codec, or bitrate options only when the product requires them.
Web MP4
- Source
- Video with a decodable video stream; audio is optional
- Artifacts
- 720p H.264/AAC MP4 and a JPG poster
// Source: Video with a decodable video stream; audio is optional
// Alias: video.web
// Preset: mp4_720p_h264_aac
// Artifacts: 720p H.264/AAC MP4 and a JPG poster
// Tier: Standard
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/source.mov",
"metadata": {
"asset_id": "asset_0426",
"recipe": "web-mp4"
},
"outputs": [
"video.web"
],
"idempotencyKey": "asset:web-mp4:v1"
});
console.log(job.id, job.status);HLS streaming
- Source
- Video with a decodable video stream; audio is optional
- Artifacts
- Master playlist, 1080p/720p variants, and six-second media segments
// Source: Video with a decodable video stream; audio is optional
// Alias: video.streaming
// Preset: hls_ladder_v1
// Artifacts: Master playlist, 1080p/720p variants, and six-second media segments
// Tier: Standard
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/feature.mp4",
"metadata": {
"asset_id": "asset_0426",
"recipe": "hls-streaming"
},
"outputs": [
"video.streaming"
],
"idempotencyKey": "asset:hls:v1"
});
console.log(job.id, job.status);Vertical social video
- Source
- Landscape, square, or portrait video
- Artifacts
- 1080×1920 H.264/AAC MP4 with a blurred 9:16 fill
// Source: Landscape, square, or portrait video
// Alias: video.social
// Preset: social_vertical_blur
// Artifacts: 1080×1920 H.264/AAC MP4 with a blurred 9:16 fill
// Tier: Premium
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/interview.mp4",
"metadata": {
"asset_id": "asset_0426",
"recipe": "vertical-social"
},
"outputs": [
"video.social"
],
"idempotencyKey": "asset:social:v1"
});
console.log(job.id, job.status);Responsive image derivatives
- Source
- JPG, PNG, WebP, or another supported still image
- Artifacts
- 1200×630 and 320×320 metadata-stripped WebP renditions
// Source: JPG, PNG, WebP, or another supported still image
// Alias: image.web
// Preset: image_multi_v1
// Artifacts: 1200×630 and 320×320 metadata-stripped WebP renditions
// Tier: Premium
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/product.png",
"metadata": {
"asset_id": "asset_0426",
"recipe": "image-derivatives"
},
"outputs": [
"image.web"
],
"idempotencyKey": "asset:image-derivatives:v1"
});
console.log(job.id, job.status);Audio plus transcript
- Source
- Audio, or video containing a decodable audio stream
- Artifacts
- 128 kbps AAC/M4A plus SRT and WebVTT transcripts
// Source: Audio, or video containing a decodable audio stream
// Alias: audio.transcription
// Preset: audio_aac_128k
// Artifacts: 128 kbps AAC/M4A plus SRT and WebVTT transcripts
// Tier: Standard
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/interview.mp4",
"metadata": {
"asset_id": "asset_0426",
"recipe": "audio-transcript"
},
"outputs": [
"audio.transcription"
],
"idempotencyKey": "asset:audio-transcript:v1"
});
console.log(job.id, job.status);Moderation plus watermarking
- Source
- One image or video; this example uses video
- Artifacts
- Watermarked 720p MP4, JPG poster, and moderation evidence report
// Source: One image or video; this example uses video
// Alias: video.web
// Preset: mp4_720p_h264_aac
// Artifacts: Watermarked 720p MP4, JPG poster, and moderation evidence report
// Tier: Premium
// Testing: use job.wait(); production: persist job.id and consume the signed webhook
// npm install @mediaruntime/node
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const job = await media.jobs.create({
"source": "https://cdn.example.com/media/upload.mp4",
"metadata": {
"asset_id": "asset_0426",
"recipe": "moderation-watermark"
},
"moderation": {
"enabled": true,
"mode": "report",
"checks": [
"sexual",
"violence",
"dangerous"
]
},
"watermark": {
"enabled": true
},
"outputs": [
"video.web"
],
"idempotencyKey": "asset:moderation-watermark:v1"
});
console.log(job.id, job.status);{ "watermark": { "enabled": true } }; MediaRuntime resolves the server-owned logo.Version the complete processing policy, not copied request JSON.
Hosted recipes are immutable, account-scoped versions of outputs, moderation, and watermark policy. Use an unpinned name for the latest active version or `name@version` when a deployment must never move.
# Discover the built-in and account recipes available to this key.
mediaruntime recipes list
# A hosted recipe may be pinned to one immutable version.
mediaruntime run ./launch.mp4 \
--recipe team-video@3 \
--download ./launch.zip
# The raw API accepts the same reference.
curl -sS -X POST "https://mediaruntime.com/v1/jobs" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "https://cdn.example.com/media/launch.mp4",
"recipe": "web-video@1",
"metadata": { "asset_id": "launch-01" }
}'recipe acknowledgement and SHA-256 digest.web-video@1, social-video@1, and ai-transcription@1.Add layered visual-safety analysis to the job.
Moderation uses a layered pipeline for one image or video input. Report mode returns best-effort evidence and continues processing. Block mode is a fail-closed pre-engine gate: block or review rejects the job, while allow continues.
{
"source": "https://cdn.example.com/upload.mp4",
"metadata": { "media_type": "video", "asset_id": "asset_0426" },
"moderation": {
"enabled": true,
"mode": "report",
"checks": ["sexual", "violence", "dangerous"]
},
"outputs": [{
"type": "mp4",
"preset": "mp4_720p_h264_aac"
}]
}{
"moderation": {
"requested": {
"enabled": true,
"mode": "report",
"checks": ["sexual", "violence", "dangerous"],
"media_type": "video",
"phase": "phase1_video_report"
},
"result": {
"ok": true,
"media_type": "video",
"verdict": "review",
"flagged_checks": ["violence"],
"scores": {
"violence": { "yes": 0.82, "no": 0.18 }
},
"decisions": {
"violence": { "decision": "review", "raw_decision": "review" }
},
"evidence": {
"frames_sampled": 8,
"frames_flagged": [{
"frame_index": 3,
"timestamp_sec": 20,
"verdict": "review",
"flagged_checks": ["violence"]
}]
},
"video": {
"frame_interval_sec": 10,
"max_frames": 24
}
}
},
"meta": {
"moderation_result": {
"url": "https://storage.googleapis.com/.../moderation_result.json"
}
},
"usage": {
"breakdown": { "moderation_units": 120 }
}
}| Contract | Value | Notes |
|---|---|---|
| Plan | Premium | The API returns 403 unless the account is Premium or auto-upgrade is allowed. |
| Mode | report or block | Report is observational. Block rejects block/review verdicts before the engine starts. |
| Checks | sexual, violence, dangerous | Send one to three checks. Omitting checks selects all three. |
| Inputs | One image or video | Audio-only inputs, batches, and Sandbox moderation are rejected. |
| Video sampling | Fixed interval, bounded frames | The service chooses the interval and cap; read the actual values from result.video and result.evidence. |
| Decision | allow, review, or block signal | Report mode never blocks execution. Block mode is fail-closed: allow continues; review or block ends as REJECTED. |
| Artifact | meta/moderation_result.json | Included in the output ZIP and exposed through meta.moderation_result.url when available. |
| Billing | Per analysed frame | One inference per frame: an image is 1 frame, a video is sampled to a cap of 24. Settled from result.evidence.frames_sampled at usage.breakdown.moderation_units. |
| Standalone | outputs may be empty | Send outputs: [] to moderate a file without transcoding it. Billed for moderation only. |
report when your application owns the publish decision. Use block when MediaRuntime should reject before transcoding; a rejected job has no output bundle and bills only moderation units. Classifier decisions remain fallible, so keep a review and appeal path where appropriate.Verify the raw bytes before trusting the event.
Terminal events are delivered at least once and ordering is not guaranteed. Verify HMAC-SHA256, reject stale timestamps, deduplicate event_id, acknowledge quickly, and move heavy work to a queue.
{
"event_id": "webhook_evt_job_1320c28b72104811b075a26a99496cf6",
"job_id": "job_1320c28b72104811b075a26a99496cf6",
"account_id": "acc_xxx",
"status": "COMPLETED",
"completedAt": "2026-08-09T02:41:23Z",
"billing": { "status": "PAID", "estimatedUnits": 31 },
"usage": { "units_total": 31, "breakdown": {} },
"delivery": {
"mode": "PULL",
"retentionDays": 7,
"expiresAt": "2026-08-16T02:41:23Z",
"bundle": {
"type": "zip",
"filename": "outputs.zip",
"download": {
"url": "https://mediaruntime.com/v1/jobs/job_1320c28b72104811b075a26a99496cf6/bundle?token=...",
"expiresAt": "2026-08-16T02:41:23Z"
}
}
},
"meta": {
"engine_result_url": "https://storage.googleapis.com/...",
"outputs_root_gs": "gs://.../jobs/acc_xxx/job_.../outputs",
"request_metadata": {
"producer": "my-api",
"entity_id": "video_01J8Y4",
"media_type": "video"
}
}
}delivery.bundle.download.url, or fetch meta.engine_result_url to enumerate individual output and artifact paths.import express from "express";
import { MediaRuntime } from "@mediaruntime/node";
const media = new MediaRuntime();
const app = express();
// Register this route before any express.json() middleware.
app.post(
"/webhooks/mediaruntime",
express.raw({ type: "application/json" }),
media.webhooks.express(async (event, _req, res) => {
// Persist and deduplicate event.id before acknowledging.
console.log(event.id, event.jobId, event.status);
res.sendStatus(204);
}),
);Delivery rules
- Return any 2xx only after signature verification and durable enqueue/deduplication.
- Treat event_id as the idempotency key.
- Use meta.request_metadata to find your entity without a second lookup table.
- Download retained outputs before delivery.expiresAt.
- A FAILED or REJECTED event has error.code/message and no usable bundle. A PARTIAL batch has error.code BATCH_PARTIAL; inspect delivery.items for each child's status and successful bundle.
Poll when a webhook is not practical.
Submission returns immediately with a job_id. Webhooks remain the lowest-latency way to learn a job finished, but polling is available for local development, environments without a public endpoint, reconciliation, and support questions.
curl -sS "https://mediaruntime.com/v1/jobs/$JOB_ID" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY"# Newest first. Filter by status and page with the cursor.
curl -sS "https://mediaruntime.com/v1/jobs?status=COMPLETED&limit=25" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY"
# Next page: pass the previous response's next_cursor
curl -sS "https://mediaruntime.com/v1/jobs?limit=25&cursor=$NEXT_CURSOR" \
-H "X-API-Key: $MEDIARUNTIME_API_KEY"{
"job_id": "job_2ee8db582cdf4a2fafb49d52218b3159",
"status": "COMPLETED",
"tier": {
"requested": "premium",
"required": "standard",
"effective": "premium",
"billed": "standard",
"reasons": []
},
"usage": { "units_total": 4 },
"billing": {
"status": "PAID",
"currency": "USD",
"unit_price_cents": 1,
"final_units": 4,
"final_amount_cents": 4
},
"bundle": {
"available": true,
"download_url": "https://mediaruntime.com/v1/jobs/job_2ee8.../bundle?token=...",
"expires_at": "2026-08-18T03:14:27Z",
"size_bytes": 13056793,
"retention_days": 7
},
"media": {
"format": "mov,mp4,m4a,3gp,3g2,mj2",
"duration_sec": 61.5,
"bit_rate": 8000000,
"video": {
"codec": "h264",
"profile": "High",
"width": 1080,
"height": 1920,
"encoded_width": 1920,
"encoded_height": 1080,
"fps": 29.97,
"rotation_deg": 90,
"is_rotated": true,
"orientation": "portrait"
},
"audio": { "codec": "aac", "sample_rate_hz": 48000, "channels": 2, "layout": "stereo" },
"streams": { "video": 1, "audio": 1, "other": 0 }
},
"metadata": { "asset_id": "asset_0426", "media_type": "video" },
"error": null,
"completed_at": "2026-08-11T03:14:53Z"
}requested is the tier of the API key that submitted the job. required is what the work actually needs, effective is the lane it ran on, and billed is what you were charged. A premium key running standard work shows requested: premium with billed: standard — you are charged for the work, not the key.media reports what MediaRuntime found in your input when it probed it at submit — the same probe that decides whether a job is accepted. When a job is REJECTED for an incompatible pairing, this explains why: an MP3 sent to an image output shows streams.video: 0, and a still sent to a frames output has no duration_sec. Note video.width/height are DISPLAY dimensions with rotation applied, so a portrait phone clip reads 1080x1920 even though encoded_width/encoded_height are 1920x1080. Every field is optional: a missing one means the probe did not report it, never zero.GET /v1/jobs/{job_id}/moderation returns the verdict alone, so a client polling for a decision does not pull billing and bundle detail every time. It answers with verdict, the per-check decision and confidence, and the escalation likelihoods. A check marked review_only is advisory: it can raise a review verdict but cannot by itself block. The endpoint returns **404 when the job exists but moderation was never requested** — an empty success response would be indistinguishable from "moderated and found nothing". The thresholds behind each decision are not published.GET /v1/jobs/{job_id}/media-report returns the media_report_v1 document without downloading the bundle. report carries it inline; an unusually large report is not stored inline, and the response then sets report to null with a download_url that still resolves, so handle both. Returns 404 when the job carries no report.GET /v1/jobs/{job_id}/compatibility-report returns the versioned compatibility_report_v1 document without downloading the ZIP. It includes five conservative profiles, rule-level expected versus actual evidence, and an existing corrective preset for incompatible profiles. This is actionable guidance, not exhaustive device certification. Handle either inline report or download_url; a job without this preset returns 404.GET /v1/jobs/{job_id}/codes returns the bounded code_detect_v1 scan without downloading the ZIP. It supports images, video and animated visual sources, plus audio files that contain embedded cover artwork; plain audio is rejected with a corrective message. The scan retains at most 12 sampled frames and 16 unique codes per frame. Treat every decoded_text value as untrusted text: never render it as HTML and never follow a decoded URL automatically. Evidence frames remain in the ZIP and are referenced by bundle path.| Field | Type | Notes |
|---|---|---|
| status | string | QUEUED, PROCESSING, COMPLETED, FAILED, REJECTED, or batch-only PARTIAL. |
| tier | object | requested / required / effective / billed, plus reasons when premium was required. |
| usage.units_total | integer | Billable units for the job. |
| billing | object | Currency, unit price, and estimated vs final units and amount. |
| bundle.download_url | string | Job-scoped, expiring bundle URL. Single-job endpoint only. |
| media | object | What the input actually was, as probed at submit. Null on older jobs. |
| media.video.width/height | integer | Display dimensions, rotation already applied. |
| media.duration_sec | number | Absent for still images, which have no timeline. |
| metadata | object | The metadata object you submitted, echoed back. |
| error | string | Populated on FAILED, REJECTED, or batch-only PARTIAL; null otherwise. |
Polling rules
- Prefer webhooks; poll only when you cannot receive one.
- Page with next_cursor, never an offset — rows shift as jobs update.
- A job id you do not own returns 404, the same as one that does not exist.
- List rows omit the bundle URL; fetch the single job to download.
- Back off between polls. A terminal status will not change.
Prepaid, pay as you go, and settled from actual usage.
Add a card, fund the wallet, and submit work without a recurring subscription. MediaRuntime reserves an estimate before execution and settles the final charge when the job reaches a terminal state.
| Plan | Starting usage price | Minimum top-up | Default auto-top-up |
|---|---|---|---|
| Standard Pay-As-You-Go | From $0.02 | $20.00 | $20.00 at $2.00 available |
| Premium Pay-As-You-Go | From $0.05 | $60.00 | $60.00 at $5.00 available |
billing and usage fields for reconciliation.Wallet rules
- Available credit equals wallet credit minus funds reserved for running jobs.
- Insufficient available credit returns HTTP 402 before execution.
- Auto-top-up is optional and requires a card on file.
- A Premium-only request returns 403 when upgrade is not permitted.
Retry transport failures, not invalid work.
Every response includes X-Request-Id. Errors add a normalized error object while preserving detail/message for compatibility. Log the request ID, code, and status—but never the API key, signed URLs, or request body.
| Status | Code | Meaning | What your integration should do |
|---|---|---|---|
| 400 | invalid_request | The request is logically invalid or the estimator rejected it. | Fix the request; do not retry unchanged. |
| 401 | authentication_error | The API key is invalid, expired, or revoked. | Correct or rotate the key; do not blindly retry. |
| 402 | billing_required | The account, wallet, or billing preflight cannot cover the job. | Fund the wallet or resolve billing first. |
| 403 | permission_denied | The plan, role, or feature gate does not permit the request. | Change the plan/request; do not retry unchanged. |
| 404 | not_found | The owned resource is absent or hidden by owner scoping. | Correct the identifier; do not retry unchanged. |
| 409 | idempotency_in_progress / conflict | An operation with this key is running or another active operation conflicts. | Retry only when error.retryable is true. |
| 410 | gone | A short-lived token expired. | Obtain a fresh result or token. |
| 413 | request_too_large | The HTTP request body exceeds 2 MiB. | Upload media separately and send URLs only. |
| 422 | validation_error / idempotency_conflict / unprocessable_entity | Validation failed or an idempotency key was reused with another body. | Correct the named field or key. |
| 429 | rate_limited | The account or key is rate limited. | Retry with exponential backoff and jitter. |
| 500 | internal_error | The gateway failed unexpectedly. | Retry safely with backoff. |
| 502 | upstream_error | A transient platform dependency failed. | Retry safely with backoff. |
| 503 | service_unavailable | A dependency or execution lane is unavailable. | Retry safely with backoff. |
{
"error": {
"code": "billing_required",
"message": "Insufficient wallet balance for this job",
"status": 402,
"retryable": false,
"request_id": "req_7fa01eec9b6248a5a7be2d60ff4bb978",
"details": null
},
"request_id": "req_7fa01eec9b6248a5a7be2d60ff4bb978",
"detail": "Insufficient wallet balance for this job"
}The small surface most integrations need.
All server-to-server endpoints below use X-API-Key. The tokenized bundle URL is the only exception because it carries its own short-lived, job-scoped credential.
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/upload-url | Optionally create a 15-minute upload target when you do not already have a fetchable media URL. |
| POST | /v1/jobs | Queue a single-input or batch media job. |
| GET | /v1/jobs/{job_id} | Status, tier decision, usage, billing, and bundle link for one job. |
| GET | /v1/jobs | List your jobs, newest first. Supports ?status= and cursor paging. |
| GET | /v1/jobs/{job_id}/moderation | Moderation verdict for one job. 404 when moderation was not requested. |
| GET | /v1/jobs/{job_id}/media-report | Forensic media report for one job. 404 when media_report_v1 was not requested. |
| GET | /v1/jobs/{job_id}/compatibility-report | Versioned compatibility verdict for one job. 404 when compatibility_report_v1 was not requested. |
| GET | /v1/jobs/{job_id}/codes | Bounded QR/barcode detections and evidence references. 404 when code_detect_v1 was not requested. |
| GET | /v1/jobs/{job_id}/bundle?token=... | Redeem the job-scoped token for a bundle; no API key is required. |
| POST | /v1/jobs/{job_id}/retry-webhook | Retry the terminal webhook for a job you own. |
| POST | /v1/account/watermark-logo/upload-url | Create an upload target for the account PNG logo. |
| POST | /v1/account/watermark-logo/confirm | Confirm the logo and its placement settings. |
Use the versioned OpenAPI 3.1 document for client generation, request validation, and contract review. It contains only the supported public API surface and requires no API key.