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://mp.dvocorp.com/api/v1/studio/tools
Each entry gives you operation_id, service_slug, operation_key,
credits_per_call, what file kinds it accepts, its tunable parameters
(config.fields[], each with key / type / min / max), and its ready-made
settings (config.presets[], each a {label, params} — the top-level presets[]
list carries only {index, label}, so read the param values from
config.presets[].params).
credits_per_call is what one job costs at submit, and it is the same on
every tool because there is one tariff. A params.operations pipeline does not
change it either: addressing a cheap operation while shipping an expensive
recipe costs exactly the same as addressing the expensive one.
The tariff: you pay for processing time
One price, every operation — photo, video, audio, subtitles, batch, API, studio:
| processing time | you pay |
|---|---|
| up to 1 min | 2 |
| 1–2 min | 3 |
| 2–3 min | 4 |
| each further started minute | +1 |
| however long it runs | never more than 30 |
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.
Nobody knows how long your file will take until a worker has run it, so the flow is:
- at submit you are charged the base (2), and
estimated_costreflects that; - when the job finishes, the difference is debited and
final_costbecomes the real total. Faster than a minute changes nothing — the base is the minimum and is never refunded down.
A job that ends failed or canceled is never charged for time and every
bonus it took is refunded. 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 them under
billing: time_tariff_base_credits, time_tariff_per_extra_minute_credits,
time_tariff_max_credits, time_tariff_enabled.
The input-length ceiling
The 30-bonus 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.
| tier | max input length, any operation |
|---|---|
| anonymous (no sign-in) | 10 min |
| free account | 30 min |
| paid plans | 180 min |
This applies to every operation, not just speech-to-text, and only to media
that reports a duration (images are never affected). Live values are on
GET /api/v1/public/config under billing.input_max_minutes.
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.
operation_key |
What it does | Bonuses | Main params |
|---|---|---|---|
compress |
Shrink for web | 3 | level light|medium|strong, crf 0–51, preset ultrafast…slow |
resize |
Change resolution | 3 | width / height 16–7680, mode fit|pad |
convert |
Change container | 3 | format mp4|webm|mov|mkv; vcodec libx264|libx265|libvpx-vp9|copy, acodec aac|libmp3lame|libopus|copy (omit → the container's default) |
social |
Social presets | 3 | preset square|vertical|widescreen, mode crop|pad, focus_x/focus_y 0–1 (where the crop sits: 0 = left/top edge, 0.5 centre) |
trim |
Cut a segment | 2 | start (s), duration (s), precise (default true) |
fps |
Change frame rate | 2 | fps 1–120 |
faststart |
Web-optimize (instant play) | 1 | — |
mute |
Remove the audio track | 1 | — |
speed |
Speed up / slow down | 3 | factor 0.1–10 |
rotate |
Rotate | 2 | mode 90cw|90ccw|180 |
flip |
Mirror | 2 | axis h|v |
gif |
Video → GIF | 3 | start, duration 1–60, fps 1–30, width 16–1920 |
thumbnail |
Grab a frame | 1 | at (s), smart, format jpg|png|webp (jpeg is accepted as an alias for jpg), width 16–3840 |
cover |
Cover image | 1 | builder-driven |
extract_audio |
Rip the audio | 2 | format mp3|aac |
enhance_voice |
Clean up voice | 3 | level light|medium|strong; denoise, deess, compress, loudnorm |
add_audio |
Voiceover / background music | 3 | tracks[], original_volume — see Add audio |
subtitles |
Burn / embed subtitles | 6 | see Subtitles |
transcribe |
Speech → SRT/VTT/TXT | 5 | format, language, task, quality fast|balanced|accurate |
auto_edit |
Cut pauses & filler words | 7 | language, max_pause 0.2–5, keep_pause 0–2, padding 0–1, remove_fillers, remove_retakes |
circle |
Round video / Telegram note | 3 | see Circle |
blur_bg |
Fit to canvas, blurred bg | 3 | see Fit to canvas |
watermark |
Overlay a watermark | 3 | builder-driven |
device_meta |
Tag as shot on a device | 2 | preset (device key), creation_time — writes container/QuickTime tags, not EXIF |
strip_metadata |
Remove metadata | 1 | — |
From a link (input is a URL, not a file)
These operations take no uploaded file. The input is a public video link in
params.url, so they are submitted with the plain JSON POST /api/v1/jobs
instead of the multipart upload — everything else (polling, the result URL,
billing) is identical.
operation_key |
What it does | Bonuses | Main params | Ships |
|---|---|---|---|---|
link_info |
Read a link's public metadata, engagement stats and comments | 2 | url, include[], comment_limit 1–500, format json|csv |
on |
download_video |
Download the video | 2 | url, quality best|2160p|1440p|1080p|720p|480p|360p, start, duration |
off |
download_audio |
Link → MP3 / M4A / Opus / WAV | 2 | url, audio_format, start, duration |
off |
download_thumbnail |
The platform's own cover image | 1 | url, thumbnail_format jpg|png|webp |
off |
link_transcribe |
Video link → text (fetch + Whisper) | 5 | url, language, transcript_format txt|srt|vtt|json, task |
off |
link_captions |
Video link → video with burned-in captions | 6 | url, language, style karaoke|boxed|clean|yellow, position |
off |
"Ships off" means exactly that. Every operation that TRANSFERS media from a
platform is present in the code and dormant: platform terms of service forbid
the transfer, and payment providers' acceptable-use policies name it. Turning
one on takes two deliberate acts — an admin enables the operation, and the
operator sets MEDIA_FETCH_ENABLED=true on the video service. Until then those
operations answer with a plain "switched off on this server". Reading a link's
public metadata (link_info) moves no media and is on by default.
# one request, no upload — the link IS the input
curl -s https://mp.dvocorp.com/api/v1/jobs/api \
-H "X-Api-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"service_slug":"clipconvert","operation_key":"link_info",
"params":{"url":"https://www.youtube.com/watch?v=VIDEO_ID",
"include":["info","stats","analytics"],"format":"json"}}'
# -> {"id":"...","status":"queued"} then poll /api/v1/jobs/<id> as usual
include[] picks what the report contains — any of info (title, description,
channel, tags, upload date), stats (views, likes, comments, subscribers),
analytics (engagement rate, views/day, likes and comments per 1k views,
views per subscriber), chapters, subtitles (which caption languages exist),
thumbnails, formats (available qualities), comments. format: "csv"
flattens the comments — or, with no comments requested, the stats — into one
spreadsheet-ready table.
Three limits worth knowing before you build against these:
- One video per job. A playlist or channel link is refused rather than silently expanded into hundreds of charged jobs.
- Live streams are refused. A stream has no length, so no cap could bind on one.
- Length. A download-only job accepts sources up to
MEDIA_MAX_DURATION_SEC(default 60 min). A job that also processes the video (link_transcribe,link_captions) obeys the ordinary input capMAX_DURATION_SEC(default 5 min) — the same ceiling an uploaded file gets, because the expensive part is the same work. - Comments are personal data. They are returned to you and never retained beyond the job's own 24-hour artifact. What you do with them afterwards is your controller obligation, not ours.
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.
| Behaviour | Operations |
|---|---|
| No encoder at all | link_info (reads a page), download_video / download_audio / download_thumbnail (a transfer, at most a mux) |
| 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:
trimaccuracy.precise: true(the default) re-encodes so the cut lands exactly onstart.precise: falsecuts on the nearest keyframe at or beforestartand copies — near-instant, but the clip can begin up to a few seconds early. A trim from0always takes the fast path; the flag only matters for a cut from mid-video.- Resolution. A re-encode is capped at 1080p. A remux is not — the video is
passed through exactly as you sent it, 4K included. Ask for a smaller file
explicitly with
resizeorcompress.
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 | Bonuses | Main params |
|---|---|---|---|
resize |
Change dimensions | 1 | width / height 16–20000, resize_mode fit|fill|exact; scale by percentage needs both percent and resize_mode: "scale" |
compress |
Shrink | 1 | quality 1–100, format jpeg|webp|avif|png, max_kb (re-encodes down to fit) |
convert |
Change format | 1 | format webp|avif|jpeg|png, quality, max_kb |
crop |
Crop to aspect | 1 | 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 | 1 | diameter, zoom, offset_x/y, bg_color (omit → transparent PNG) |
transform |
Rotate / flip | 1 | rotate 0–359 counter-clockwise (90/180/270 lossless, other angles fill the corners with bg_color), flip h|v |
enhance |
Filters | 1 | autocontrast, brightness / contrast / saturation / sharpness 0–4 (1 = unchanged), blur 0–100 px, grayscale |
social |
Social sizes | 1 | any width/height 16–20000, resize_mode (presets use fill) |
web_optimize |
Optimize for web | 1 | format, quality, max_kb, strips metadata |
strip_meta |
Remove EXIF / GPS | 1 | strip_metadata, strip_gps; GPS only → {"metadata_mode": "merge", "strip_gps": true} |
device_meta |
Write camera EXIF | 2 | preset (see below), metadata_mode: "set" |
watermark |
Overlay a watermark | 1 | text or logo_url, position (9-grid tl…br), opacity 0.05–1, size (% of width), color |
edit |
Everything in one pass | 2 | 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 (
watermarkwith no text and no logo,editwith 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
extract_audio,mute,enhance_voiceandtranscribe— and the input is a video file, not an audio file. These operations read the audio track out of a video container; handing them a bare.mp3/.wav/.aacis refused with400 this operation works on video files — the file you sent looks like audio. To process a standalone audio file, first wrap it in a video container (e.g. a still image + the audio track) and submit that.