Developer guide
Online video compression API guide
How to design a product-grade compression workflow with uploads, async jobs, preview renders, polling, webhooks, credit estimates, and output reports.

Bolting video compression onto a product looks, from the outside, like calling an encoder and returning a download link. From the inside it is a distributed-systems problem: uploads are large and flaky, encoding takes minutes, jobs fail for opaque reasons, users need progress and cost visibility, and every file you touch creates a retention obligation. The teams that ship this well are the ones that model those realities explicitly instead of hiding them behind a synchronous request.
This guide is the architecture that survives contact with production: compression as an asynchronous job with a real lifecycle, uploads separated from compute, previews treated as first-class, cost exposed before work starts, outputs returned as auditable reports, failures encoded as machine-readable events, and retention planned from day one. Each section gives the contract a backend should offer and the reason it matters.
Section 1Model compression as a job lifecycle
Compression is slow and stateful, so it cannot be a single blocking request. Model it as a job that moves through explicit states, accepted, queued, processing, preview-ready, completed, failed, each persisted and queryable. The client creates a job, then reads its status; the server works asynchronously and reports progress. This is the difference between a request that times out and a request that succeeds.
Durable state matters because encoding outlives any single connection. If a worker restarts mid-job, the job record tells the new worker what to resume; if a client disconnects, polling or a webhook catches the result when it lands. Treat the job table as the source of truth and the HTTP layer as a thin window onto it.
Expose the state machine to the client, not just a spinner. Let integrators poll a status endpoint or register a webhook, and let them see which stage a job is in. Predictable states are what let a frontend show honest progress instead of a fake loading bar, and what let an operator debug a stall.
A typical status response carries a job id, a state, a progress fraction, an updated timestamp, and a link to the result or error once terminal. That shape lets a client render accurate progress, detect a stalled job by a stale timestamp, and react to a terminal state without re-fetching the whole history, all from one durable record.
Section 2Separate upload from processing
Uploading a large file directly through your API process couples two things that fail for different reasons: network transfer, which is slow and drops, and encoding, which is CPU-bound and stateful. Separate them. Accept the upload to object storage, ideally via a presigned URL that lets the browser write straight to the bucket, and have the worker process by reference, not by request body.
Decoupling pays off in three places. Uploads become resumable and retryable without touching the encoder. Deduplication becomes possible: hash the source, assign it a stable source id, and skip re-encoding when the same bytes arrive again. And your API servers stay free to handle requests instead of holding gigabytes in memory.
The contract to give integrators is a two-step flow: request an upload target, deliver the bytes, then create a job against the stored source. Each step has its own errors and retries, which is exactly what you want when a fifty-megabyte upload and a CPU encode should never fail as one unit.
In concrete terms: the client calls an endpoint that returns a presigned URL and a source id, PUTs the file straight to storage, then POSTs a job referencing that source id. A retried upload does not create a second source; a retried job creation does not re-upload. Every failure is now localised to the step that actually failed, and each one is independently recoverable.
Make preview jobs first-class
The most expensive mistake a compression API enables is letting a user queue a full render of settings that were wrong from the first frame. Previews exist to catch that early and cheaply. Treat a frame sample, a first-ten-second render, and a custom-range render as their own job types, small, fast, and priced accordingly, so integrators can validate before committing compute.
First-class previews change the product. A user who can see the risky section at the proposed settings in a few seconds will iterate on settings instead of gambling on a full export. That means fewer wasted full renders, fewer refunds, and happier users, all because the API offered a cheap probe before the expensive job.
Give previews the same lifecycle as full jobs: a status, a result, and a cost, just smaller. The integrator's code is then identical between a preview and a full render, which makes it trivial to insert a validation step before the expensive call. Previews are not a feature; they are the API's quality gate.
An API might expose a mode field, frame, clip, range, full, each returning the same job shape at a different scale and price. An integrator can then run a frame preview for cents, confirm the output looks right, and only then submit the full render. The cheap probe is what turns an expensive guess into an informed decision, and it is what keeps users from churning after their first wasted render.

Expose cost before queueing work
Surprise charges are the fastest way to lose trust. Before any compute-heavy work starts, return an estimate: the credits the job will cost, the expected output size, and a rough duration. Let the user consent, then reserve the credits. Charging a user for a job they did not understand the size of is a support ticket waiting to happen.
Reservation with refund-on-failure is the robust pattern. Reserve credits when the job is accepted, debit when it succeeds, and refund when it fails. That way a crashed encode does not bill the user, and you are not chasing partial charges. The estimate does not have to be perfect; it has to be honest and visible before the spend.
This also protects the system. A cost gate lets you refuse work that would blow a quota or a plan limit up front, with a clear message and a path to upgrade, instead of starting an encode you will have to interrupt. Cost visibility is a product feature, a trust feature, and an abuse-defense feature all at once.
A clean estimate response says, in plain fields: this job will cost roughly N credits, produce about X megabytes, and take around Y seconds, based on the source's duration and resolution. The user sees the price before the work starts, consents once, and is never surprised, and you get to refuse a job that would bankrupt a small plan before spending a dollar of compute on it.
Section 5Return reports instead of bare URLs
A download URL is the smallest possible answer, and it leaves every important question unanswered. Was the output actually smaller? What codec and settings produced it? Did the encoder emit warnings? When does the file expire? A product-grade API returns a report: input and output bytes, the codec and container, the settings used, any warnings, and the expiry and checksum of the result.
Reports turn compression into an auditable workflow. An integrator can show the user the before-and-after size and the savings, log what was done for compliance, and verify the downloaded file against a checksum. A bare URL gives none of that; the integrator has to guess or re-probe.
Make the report durable even after the file is gone. Once retention expires and the bytes are deleted, the report can remain as a record that the job ran, what it produced, and why, useful for billing disputes, support, and analytics. The report is the product; the file is one of its fields.
Picture the report object: input bytes, output bytes, percent saved, codec and container, the exact settings applied, a warnings array ("audio normalised to -14 LUFS," "source had variable frame rate"), an expiry timestamp, and a checksum. That single object lets an integrator build a honest results screen, prove what happened for compliance, and trust the download, none of which a bare URL can do.
Design retry and idempotency rules
Networks are unreliable, and clients retry. Without protection, a flaky connection on job creation produces two identical encodes and two charges. The fix is an idempotency key: the client sends a unique key with the create request, the server deduplicates on it, and a retry of the same key returns the original job instead of starting a new one.
Idempotency has to cover the full create path, not just the first hop. The same key, retried seconds or minutes later, must resolve to the same job whether that job is still queued, mid-flight, or already complete. Anything less leaves a window for duplicate work, and clients will find that window under load.
Classify failures by whether they are safe to retry. A network blip is retryable; an unsupported source format is not. Tell the client which is which, so retries help instead of hammering a permanent error. Retry semantics and idempotency together are what make an unreliable network feel reliable to the integrator.
Concretely, every create request should accept a client-supplied idempotency key, and the server must treat the first key it sees as authoritative: a second request with the same key returns the first job's current state, whether that is queued, processing, or done. Pair that with a retryable flag on every error, and a client can recover from any blip safely without ever producing a duplicate encode.

Give failures machine-readable meaning
A job that fails with a generic "something went wrong" forces a human to investigate every error. A job that fails with a structured code, source_unsupported, duration_exceeds_plan, transcode_failed, quota_exceeded, lets the client react correctly: prompt the user to re-encode the source, offer an upgrade, retry once, or stop. Machine-readable failures turn support load into automatic handling.
Each code should carry enough context to act on without a round trip: what failed, which input or setting caused it, and whether it is retryable. Pair the code with a human-readable detail for logs and support, but make the code the contract. Clients program against codes; humans read details.
This is also how you find the real bottlenecks. Aggregated failure codes show you whether your pain is unsupported sources, plan limits, or encoder crashes, each of which has a different fix. Without structured failures, you are reading support tickets to do the work the error code should have done for you.
An error object might read: code "duration_exceeds_plan," detail "source is 14 minutes; plan allows 10," retryable false, with a pointer to the field that triggered it. A client can render that as an actionable message and an upgrade prompt; an operator can aggregate the codes to see that duration limits, not encoder bugs, are the top failure this week. The code did the triage automatically.
Section 8Plan file retention and deletion
Every file you accept is a storage cost and, increasingly, a liability. Decide retention up front: how long the source and output live, who can delete them, and what happens on expiry. Default to short retention, long enough for the user to download, not long enough to accumulate, and make deletion explicit and auditable.
Expose deletion in the API. Let users delete their source and output on demand, and let them see when auto-expiry will happen. A delete route is a privacy feature, a cost control, and a trust signal: the user can prove the file is gone, and you stop paying to store it.
Keep an audit record of the lifecycle without keeping the bytes. When a file is deleted, record what was deleted, when, and by whom, so you can answer a user asking where their file went. Retention is not a cleanup chore tagged onto the end; it is a first-class part of the contract, planned when the job is created.
A sensible default looks like this: sources and outputs expire after a short window unless the user explicitly keeps them, a delete endpoint removes them immediately on request, and every deletion writes a tombstone record, what, when, who, that survives the file itself. The bytes are ephemeral by design; the proof of what happened to them is permanent.
Compression decision checklist
- Create stable job ids and source ids
- Support frame, first-10-second, and custom-range previews
- Return source bytes, output bytes, codec, warnings, and expiry
- Use idempotency keys around job creation
- Expose credits before compute-heavy work starts
The throughline of every section here is that compression in a product is a system, not a function call. Model it as a durable job lifecycle, decouple upload from compute, make previews first-class, expose cost before work, return auditable reports, deduplicate with idempotency, encode failures as structured events, and plan retention from the first commit. Build those contracts and the service stays trustworthy under load, fails in ways clients can handle, and keeps every file's lifecycle honest from upload to deletion.
