Video transcoding API for uploads
Upload an MP4, MOV or WebM file up to 500 MB, then request MP4, WebM, MOV, animated GIF or HLS by adding parameters to its CDN URL. Thumbnails, trimming and audio extraction use the same URL. Heavy jobs return 202 Accepted while they run: request the same URL again until it returns 200, after which the rendition is served from cache.
How video processing works
Upload
MP4, MOV or WebM, up to 500 MB
Request a rendition URL
?format=webm&width=1280
202 Accepted
Re-encode, HLS or GIF queued
Poll the same URL
Back off between attempts
200 OK
Served from cache from then on
Upload a video
Videos are usually too large to proxy through your own API, so use the presigned flow: your backend asks for a single-use upload URL, the client PUTs the file, your backend confirms. The Go SDK wraps all three steps in UploadPresigned.
import { Dreep } from "dreep";
const dreep = new Dreep({ apiKey: process.env.DREEP_API_KEY });
// Server-side: get a single-use upload URL
const upload = await dreep.presignUpload({
filename: "launch.mov",
contentType: "video/quicktime",
sizeBytes: 184_000_000,
folder: "videos/2026",
});
// Browser or worker: PUT the raw bytes, then confirm server-side
await fetch(upload.uploadUrl, { method: "PUT", body: file });
const asset = await dreep.confirmUpload({ id: upload.id });
const hls = dreep.url(asset, { format: "m3u8" });
const webm = dreep.url(asset, { format: "webm", videoCodec: "vp9", width: 1280 });
const poster = dreep.url(asset, { format: "webp", width: 1280 });f, _ := os.Open("launch.mov")
defer f.Close()
info, _ := f.Stat()
// Presign, PUT and confirm in one call
asset, err := client.UploadPresigned(ctx, dreep.DirectUploadOptions{
PresignOptions: dreep.PresignOptions{
Filename: "launch.mov",
ContentType: "video/quicktime",
SizeBytes: info.Size(),
Destination: dreep.Destination{Folder: "videos/2026"},
},
File: f,
})
if err != nil {
log.Fatal(err)
}
hls := client.URL(asset.ID, &dreep.Transform{Format: dreep.FormatM3U8})
mp4 := client.URL(asset.ID, &dreep.Transform{
Format: dreep.FormatMP4, VideoCodec: dreep.CodecH264, Width: 1280,
})curl -X POST https://api.dreep.cloud/api/v1/upload/presign \
-H "Authorization: Bearer $DREEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"launch.mov","contentType":"video/quicktime","sizeBytes":184000000,"folder":"videos/2026"}'
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/quicktime" --data-binary @launch.mov
curl -X POST "https://api.dreep.cloud/api/v1/upload/$UPLOAD_ID/confirm" \
-H "Authorization: Bearer $DREEP_API_KEY"What one upload gives you
HLS for longer videos
The player fetches small segments as needed instead of downloading one large file, so playback starts sooner and seeking does not re-download the video.
MP4 and WebM fallbacks
H.264 MP4 for the widest playback support, VP9 WebM where you want smaller files, both from the same source and the same URL pattern.
Posters, previews and clips
A thumbnail at any second, a short GIF for hover previews, or a trimmed highlight, without running a separate FFmpeg pipeline.
Audio on its own
Request mp3, wav or aac to strip the video track, for podcast feeds or as input to your own transcription step.
Common renditions as URLs
720p MP4, H.264
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?width=1280&height=720&fit=contain&videoCodec=h264&quality=80WebM, VP9
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?format=webm&videoCodec=vp9&width=1280HLS manifest and segments
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.m3u815-second clip from 0:10
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?trimStart=10&trimEnd=253-second GIF preview at 12 fps
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.gif?trimStart=0&trimEnd=3&fps=12&width=480Thumbnail at 5 seconds
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?format=webp&time=5&width=640Audio track as MP3
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?format=mp3
Video parameters
| Parameter | Values | What it does |
|---|---|---|
| format | mp4, webm, mov, gif, hls or m3u8 | Output container. An image format (jpeg, png, webp) returns a thumbnail; mp3, wav or aac returns the audio track. |
| width, height | Pixels | Target resolution. |
| fit | cover, contain, fill | Scaling when both width and height are set. |
| quality | 1 to 100 (default 80) | Encoding quality. |
| videoCodec | h264, vp9, hevc | Video codec. H.264 is the default for broad playback support. |
| fps | 1 to 120 | Output frame rate. Lower it for GIFs to keep them small. |
| trimStart, trimEnd | Seconds | Cut a clip from the source. |
| time | Seconds | Frame to capture for a thumbnail. Values past the end use the last frame. |
| p | A preset key | Applies a saved video preset, e.g. a 720p HLS recipe. |
The async model, in detail
A remux, such as MOV to MP4 without re-encoding, is handled inline and returns 200. Re-encodes, HLS packaging and GIF conversion run in the background. The first request returns 202 Accepted. A browser that sends Accept: text/html gets a processing page that refreshes itself. An API client gets a JSON body, { "error": "Asset is processing" }. Once the job finishes, the same URL returns 200 and the rendition is served from cache.
There are no completion webhooks or job callbacks today, so readiness is something you poll for. Good practice: warm the renditions you need from a background job straight after confirm, so viewers rarely meet a 202. Start polling at around 2 seconds, double the delay each time up to about 30 seconds, add a little jitter, and stop after a bounded number of attempts and try again later rather than polling forever. Treat any status other than 200 or 202 as a failure and surface it. Once a URL returns 200, store it and stop polling.
Polling a rendition until it is ready
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Resolves when the rendition is ready; does not download the body
const waitForRendition = async (url, { maxAttempts = 15 } = {}) => {
let delay = 2000;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, { headers: { Accept: "application/json" } });
await res.body?.cancel();
if (res.status === 200) return url;
if (res.status !== 202) throw new Error(`Transform failed: ${res.status}`);
await sleep(delay + Math.random() * 500); // jitter
delay = Math.min(delay * 2, 30_000);
}
throw new Error("Still processing; try again later");
};
const ready = await waitForRendition(dreep.url(asset, { format: "m3u8" }));func waitForRendition(ctx context.Context, url string) error {
delay := 2 * time.Second
for attempt := 0; attempt < 15; attempt++ {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return nil
case http.StatusAccepted:
// still processing
default:
return fmt.Errorf("transform failed: %s", resp.Status)
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
delay = min(delay*2, 30*time.Second)
}
return errors.New("still processing")
}# 202 while processing, 200 once the rendition is cached
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Accept: application/json" \
"https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.mp4?format=webm&videoCodec=vp9&width=1280"HLS playback with hls.js
Use the .m3u8 URL as the source. Safari plays HLS natively; Chrome and Firefox need a library such as hls.js. Request the manifest once after upload to warm it, so viewers get a 200 rather than a 202.
import Hls from "hls.js";
const video = document.querySelector("video");
const src = "https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.m3u8";
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
// Safari plays HLS natively
video.src = src;
}Video transformation quotas and add-ons
When you reach a limit the API returns LIMIT_EXCEEDED (a DreepLimitError in the JavaScript SDK). Nothing is billed automatically: you buy a pack or upgrade. Size your plan around the renditions you need per upload. HLS, an MP4 fallback and a GIF preview are three different transformations of the same video.
| Plan | Price | Video transformations / month | Storage |
|---|---|---|---|
| Free | $0 | 20 | 1 GB |
| Starter | $2.99/mo or $29/yr | 1,000 | 10 GB |
| Pro | $5.99/mo or $59/yr | 4,000 | 50 GB |
Who this is for
- Apps that accept user video uploads and need web-friendly MP4, WebM or HLS renditions.
- Teams that want thumbnails, GIF previews and clips without running FFmpeg workers.
- Products where a short processing delay after upload is acceptable.
When Dreep is not the right fit
- You need job completion webhooks or callbacks. Dreep has none today; you poll the URL.
- You need live streaming. Dreep processes uploaded files only.
- You need DRM for premium content. Signed URLs limit access, but they are not DRM.
- You want a hosted video player. Dreep delivers files and HLS manifests; you bring the player.
- Your source files are larger than 500 MB.
Frequently asked questions
How do I know when a video transcode has finished?
Request the rendition URL. 202 Accepted means it is still processing; 200 means it is ready and cached. Poll with exponential backoff. There are no completion webhooks today.
Which input and output formats are supported?
Inputs are MP4, MOV and WebM up to 500 MB. Outputs are MP4, WebM, MOV, animated GIF and HLS (m3u8 manifest and segments), plus thumbnails in JPEG, PNG or WebP and audio in MP3, WAV or AAC.
How do I play HLS from Dreep?
Request the asset with a .m3u8 extension or format=hls. Safari plays it natively; in other browsers use a library such as hls.js and attach it to a video element.
How do I generate a video thumbnail?
Request an image format on the video with the time parameter, for example ?format=webp&time=5. If time is past the end of the video, the last frame is used.
What counts as a video transformation?
Requesting a transformed rendition of a video, such as an HLS package, a WebM encode, a GIF or a thumbnail, uses your monthly video transformation quota: 20 on Free, 1,000 on Starter and 4,000 on Pro. Image transformations have a separate quota.
Can I keep videos private?
Yes. Upload into a Signed folder and serve renditions through time-limited signed URLs. Transform parameters can still vary on a signed URL.
Related
Secure file uploads
Presigned browser uploads for large video files.
Image CDN API
Resize and convert images by URL, with srcset examples.
Image transformations for Cloudflare R2
Process media stored in your own R2 bucket.
Bring your own bucket
Keep originals and renditions in your own S3-compatible bucket.
SDKs
JavaScript and Go clients for uploads and URLs.
Pricing
Video quotas per plan and add-on packs.
Video transformation docs
Every video parameter with examples.
Turn one upload into MP4, HLS and a poster frame
The Free plan includes 20 video transformations a month. Upload a clip and request its HLS manifest.