3. Operations catalog

Every operation is addressed by a service_slug + operation_key pair and takes its input as params.file_url.

The live, always-current list is one public request (no key needed) — this is the same data the panel's catalog renders from:

curl -s https://metaplatho.com/api/v1/studio/tools

Each entry gives you operation_id, service_slug, operation_key, its rate card tariff_class and that card's base credits_per_call, what file kinds it accepts, its tunable parameters (config.fields[], each with key / type / min / max), and its ready-made settings (presets[], each {index, label, params}), the rest of its params (config.api_params[], each {key, default, values}) and example — a {service_slug, operation_key, params} that works as-is.

credits_per_call is what one job costs at submit — the BASE of the operation's rate card (tariff_class), the floor of the job's cost and not its total. A params.operations pipeline is priced at the dearest class of any step in it: addressing a cheap operation while shipping an expensive recipe costs exactly what addressing the expensive one does.

The tariff: you pay for processing time, at one of four rates

Every operation is metered by the worker's clock. The rate depends on the operation's class (the shipped defaults; the live numbers are in GET /api/v1/public/config → billing.tariff_classes):

class what is in it base / file + per started minute never more than
photo every image operation 1 1 5
light video with no re-encode: remux, keyframe trim, one frame, audio-only 1 1 10
standard video re-encodes: compress, convert, resize, social, effects 2 2 40
speech subtitles, transcribe, auto-edit — speech recognition on a GPU 5 3 60

So a standard job: up to 1 min of processing costs 2, 1–2 min costs 4, 2–3 min costs 6, and never more than 40 for one file. A started minute is a whole minute: 61 seconds is two minutes. Processing time is measured on the worker from the moment it picks your file up — the queue wait is not billed.

Ask before you submit. POST /api/v1/studio/quote (public, no auth, moves no money) prices real files on real tools from what you already know about them:

curl -s -X POST https://metaplatho.com/api/v1/studio/quote -H 'Content-Type: application/json' -d '{
  "files": [{"kind": "video", "duration_sec": 300, "width": 1920, "height": 1080, "size_bytes": 52000000}],
  "operation_ids": [44, 51]
}'

Each tool in the answer carries its tariff_class, the card's three numbers, and per file a likely cost, a max (the cap) and an est_processing_sec; known: false means the length was guessed from the bytes. The studio shows exactly this on every tile the moment a file is dropped.

Nobody knows how long your file will take until a worker has run it, so the flow is:

A job that ends failed or canceled is never charged for time. If it ended within its first 60 seconds of worker time, everything is refunded and final_cost is 0; after that the base is kept (final_cost = base) — the worker ran the file and what it hit was the input. An input over your tier's length limit is always refunded in full. If we cannot measure the time at all, you pay the base and nothing more.

Don't hardcode these numbers — GET /api/v1/public/config returns the four cards under billing.tariff_classes (base_credits, per_minute_credits, max_credits, plus the estimate's work_pct and fixed_sec) and the switch time_tariff_enabled. The historical time_tariff_* fields are the standard card under their old names.

The input ceilings: length and size

The per-file cap bounds your bill; it does not bound our workers. So an input longer than your tier allows is refused, not truncated: the job ends failed, every bonus it took is refunded, and error names the length, the limit and your tier. A file bigger than your tier allows never gets that far: it is refused with 413 when the upload is opened (/uploads/multipart/create, /uploads/multipart/complete, upload links), before a byte is stored.

tier max input length, any operation max size per file
anonymous (no sign-in) 5 min 300 MB
free account 5 min 1 GB
paid plans 45 min 6 GB

The length ceiling applies to every operation, not just speech-to-text, and only to media that reports a duration (images are never affected); the size cap applies to every file. Live values are on GET /api/v1/public/config under billing.input_max_minutes and billing.input_max_mb (billing.video_max_mb / video_max_sec carry the largest tier's pair for older clients).

Split a longer recording yourself (trim is a remux — it is cheap and lossless) and submit the parts.

Bulk works the same way, per file. A /studio/batch is charged the base per file at submit and each file's own processing time afterwards, and the length ceiling applies per file too. A file longer than your tier allows stops the batch: the remaining files are canceled, the batch ends failed with the filename in error, and everything not already delivered is refunded, the over-long file included. Files delivered before it stay charged.

Some jobs — compress, resize, convert, watermark, add/strip metadata — exist on both services, once for video and once for images. Those entries share a config.family value. The web studio uses it to show a single button and picks the operation from the file you dropped; over the API you address the two operations separately, as usual, by service_slug + operation_key. The tables below are the shipped baseline — an admin can add or retune operations, so treat /studio/tools as the source of truth.

ClipConvert — video ("service_slug": "clipconvert")

Every operation accepts video; transcribe also takes audio-only files. Class is the rate card the job is priced at — photo 1 (+1/min, cap 5), light 1 (+1/min, cap 10), standard 2 (+2/min, cap 40), speech 5 (+3/min, cap 60) at the time of writing. The base debited at submit is the card's base_credits; the live number per operation is credits_per_call in GET /api/v1/studio/tools, the cards themselves in GET /api/v1/public/config → billing.tariff_classes. Two operations change class with their params: subtitles with your own text, cues or a file is standard (light with mode: "embed"), and auto_edit with cuts is standard — no speech is recognised in either case.

operation_key What it does Class Main params
compress Shrink for web standard level light|medium|strong (medium), crf 0–51, preset ultrafast…veryslow (veryfast), max_mb 0–2000 (fit into this size — see below)
resize Change resolution standard width / height (or long_side), mode fit|pad, bg_color for the letterbox (#000000)
convert Change container standard format mp4|webm|mov|mkv; vcodec libx264|libx265|libvpx-vp9|copy, acodec aac|libmp3lame|libopus|copy (omit → the container's default)
social Social presets standard preset square|vertical|widescreen (required), mode crop|pad, focus_x/focus_y 0–1 (where the crop sits: 0 = left/top edge, 0.5 centre)
trim Cut a segment light start (s), duration (s), precise (default true)
fps Change frame rate standard fps 1–120
faststart Web-optimize (instant play) light —
mute Remove the audio track light —
speed Speed up / slow down standard factor >0.1–10
rotate Rotate standard mode 90cw|90ccw|180
flip Mirror standard axis h|v
gif Video → GIF standard start, duration ≤60, fps 1–30 (15), width 16–1920 (600)
thumbnail Grab a frame light at (s), smart, format jpg|png|webp (jpeg is accepted as an alias for jpg), width 16–3840
cover Cover image light the studio's cover picker; over the API call thumbnail with {"smart": true} (and at) — it is listed in the catalog but has no params of its own
extract_audio Rip the audio light format mp3|aac
enhance_voice Clean up voice light level light|medium|strong; denoise, deess, compress, loudnorm
add_audio Voiceover / background music light tracks[], original_volume — see Add audio
subtitles Burn / embed subtitles speech see Subtitles
transcribe Speech → SRT/VTT/TXT speech format, language, language_hint, task, quality fast|balanced|accurate, typography
auto_edit Cut pauses & filler words speech language, max_pause 0.2–5, keep_pause 0–2, padding 0–1 (0.12), remove_fillers, remove_retakes, filler_words[] ≤50
circle Round video / Telegram note standard see Circle
blur_bg Fit to canvas, blurred bg standard see Fit to canvas
watermark Overlay a watermark standard text (≤120) or logo_url; position tl|tc|tr|ml|mc|mr|bl|bc|br|center (default br), opacity 0.05–1 (0.5), size 0–100 % of width (6), margin 0–40 (2), color (#ffffff)
device_meta Tag as shot on a device light preset (device key), creation_time — writes container/QuickTime tags, not EXIF
strip_metadata Remove metadata light —

Fit into a size: compress + max_mb. The output comes out no bigger than max_mb megabytes (1 MB = 1 048 576 bytes) in ONE pass — the video bitrate is capped from the duration ((max_mb·8192·0.95 / seconds) − audio), the quality setting still applies below the cap, and if the cap falls under ~1.5 Mbit/s the video steps down to 720p. {"max_mb": 49} fits a Telegram bot's 50 MB upload. Too small a target for the length fails before any work with max_mb 1 is too small for a 600-second video — at least 19.0 MB. result.body.size_target reports {max_mb, size_mb, met, video_kbps_cap, audio_kbps, downscaled_to_720p}.

Which video operations re-encode (and which don't)

An operation that changes no pixels is a remux: the picture is copied packet for packet instead of being re-encoded. It comes back in seconds instead of minutes, and it is bit-identical — no quality loss, no bitrate change.

Frame rate on re-encodes. A re-encode keeps the source frame rate up to 60 fps; faster footage (120/240 fps slow-motion from a phone) comes out at 60 fps — the result reports fps_capped_from. The Business plan has no cap. An explicit fps operation always wins, and a remux keeps the original rate.

Input resolution. Video up to 4K (3840×2160, either orientation) on every plan; Pro and Business take up to 8K (8192×4320 — GoPro 5.3K, DJI, 8K phones, 5K screen recordings). Larger input fails with video too large. Re-encodes of big inputs come out web-sized either way.

Behaviour Operations
Remux — nothing is re-encoded faststart, mute, strip_metadata, device_meta, trim from 0 (or with precise: false), convert when the codecs already fit the new container (e.g. .mov H.264 → .mp4), subtitles with mode: "embed", extract_audio when the source track already is that format (AAC → aac)
Sound only — the picture is copied enhance_voice, add_audio
Full re-encode — required compress, resize, social, fps, rotate, flip, speed, watermark, blur_bg, circle, gif, subtitles with mode: "burn", auto_edit, convert to a different codec (anything → webm)

Two consequences worth knowing:

The finished job reports which path actually ran at result.body.encode_mode — one of remux, video_copy, audio_copy, encode.

PhotoConvert — images ("service_slug": "photoconvert")

Every operation accepts image.

operation_key What it does Class Main params
resize Change dimensions photo width / height 16–20000 or long_side, resize_mode fit|fill|exact|pad|long, upscale, bg_color (pad); scale by percentage needs both percent and resize_mode: "scale"
compress Shrink photo quality 1–100, format jpeg|webp|avif|png (also tiff, bmp, gif, heic), lossless, max_kb (re-encodes down to fit)
convert Change format photo format webp|avif|jpeg|png, quality, max_kb
crop Crop to aspect photo box [left, top, right, bottom] px, or crop_aspect 1:1|4:5|5:4|4:3|3:4|3:2|2:3|16:9|9:16|21:9, gravity center|top|bottom|left|right
circle Round avatar photo diameter 16–8000, zoom, offset_x/y, bg_color (omit → transparent PNG)
transform Rotate / flip photo rotate 0–359 counter-clockwise (90/180/270 lossless, other angles fill the corners with bg_color), flip h|v
enhance Filters photo autocontrast, brightness / contrast / saturation / sharpness 0–4 (1 = unchanged), blur 0–100 px, grayscale
social Social sizes photo any width/height 16–20000, resize_mode (presets use fill)
web_optimize Optimize for web photo format, quality, max_kb; add "strip_metadata": true (and "strip_gps": true) to drop EXIF/GPS — not implicit
strip_meta Remove EXIF / GPS photo strip_metadata, strip_gps; GPS only → {"metadata_mode": "merge", "strip_gps": true}
device_meta Write camera EXIF photo preset (see below), metadata_mode: "set", fields (extra tags), gps
watermark Overlay a watermark photo text or logo_url, position (9-grid tl…br), opacity 0.05–1, size (% of width), margin 0–40, color
edit Everything in one pass photo composes a full pipeline

device_meta presets: iphone, iphone_15, iphone_15_pro_max, iphone_16, iphone_16_pro, iphone_16_pro_max, samsung_galaxy_s24, samsung_galaxy_s24_ultra, samsung_galaxy_s25_ultra, google_pixel_8_pro, google_pixel_9_pro, xiaomi_14_ultra, oneplus_12, sony_xperia_1_vi, huawei_p60_pro, sony_a7_iv, canon_eos_r5, canon_eos_r6_mark_ii, nikon_z8, fujifilm_x_t5.

Send the params. An image operation whose params say nothing to do (watermark with no text and no logo, edit with no pipeline) is rejected with an error — it is never accepted, billed, and answered with your own file back.

There is no separate audio service. Audio work is done by the ClipConvert operations: transcribe takes an audio-only file directly (MP3, WAV, M4A, OGG/Opus, FLAC — its accepts is ["video", "audio"]); extract_audio, mute and enhance_voice read the audio track out of a video and refuse a bare .mp3/.wav with 400 this operation works on video files — the file you sent looks like audio.

DocConvert — PDF ("service_slug": "docconvert")

PDF in and out. merge_pdfs and images_to_pdf take several files as one job — send them in order as repeated files fields (or params.file_urls on /jobs/api); everything else takes one PDF. Every result is a download (Content-Disposition: attachment).

operation_key What it does Class Main params
images_to_pdf Photos / scans → one PDF, in the order sent photo page_size fit|a4|letter (fit = each page the photo's size), orientation auto|portrait|landscape, margin_mm 0–50
merge_pdfs Several PDFs → one, in the order sent photo — (2 or more files)
compress_pdf Smaller PDF — pictures inside recompressed, text untouched photo level low|medium|high (default medium)
pdf_to_images Pages → JPG / PNG / WebP photo format jpg|png|webp, dpi 50–300 (150), quality 1–100 (85), pages
pdf_to_text The PDF's text as a UTF-8 .txt photo pages

pages picks pages, 1-based: "1-3,5", "2-" (to the end), "last"; empty = all. One page from pdf_to_images comes back as a picture, several as a ZIP of page-001.jpg, … (result.outputs[0].contains lists them).

Several photos → one PDF (the Telegram "album → PDF" case):

curl -s https://metaplatho.com/api/v1/jobs/upload -H 'X-Api-Key: ca_live_...' \
  -F '[email protected]' -F '[email protected]' -F '[email protected]' \
  -F 'service_slug=docconvert' -F 'operation_key=images_to_pdf' \
  -F 'params={"page_size": "a4", "margin_mm": 10}'

Bigger than ~100 MB in total? Upload each file first (POST /api/v1/uploads), then send the URLs in order:

{ "service_slug": "docconvert", "operation_key": "merge_pdfs",
  "params": { "file_urls": ["https://.../a.pdf", "https://.../b.pdf"] } }

Limits and errors: