Create an upscale
POST /api/v1/upscale — charge credits for a video and start upscaling it to 1080p, 2K or 4K.
POST https://videoupscaler.co/api/v1/upscaleStarts an upscale and returns the new task. We download the video, read its real duration from the file and charge credits for it before the job starts. You never send the duration yourself.
Request
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer <your API key> |
Content-Type | Yes | application/json |
Idempotency-Key | Recommended | Any string of 1–255 printable ASCII characters. Retrying with the same key never charges twice. See Idempotency. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
video_url | string | Yes | A public http(s) URL of the video, or the video_url returned by /uploads. |
resolution | string | Yes | 1080p, 2k or 4k. |
Video requirements
- Formats: MP4, MOV and WebM.
- Length: up to 600 seconds.
- Size: up to 500MB for a remote
video_url, 100MB through /uploads. - The server at
video_urlmust answer a plainGETwith the file and aContent-Lengthheader. Signed links from S3, R2 or Google Cloud Storage work. Share pages such as YouTube or Google Drive previews do not. - Fragmented MP4 files without a total duration are rejected. Re-encode them into a regular MP4 first.
- WebM files must store their duration in the header. Recordings made in a browser (MediaRecorder) often don't; re-encode them first.
Example
curl https://videoupscaler.co/api/v1/upscale \
-H "Authorization: Bearer $VIDEOUPSCALER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1234" \
-d '{"video_url": "https://example.com/clip.mp4", "resolution": "2k"}'const res = await fetch('https://videoupscaler.co/api/v1/upscale', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEOUPSCALER_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-1234',
},
body: JSON.stringify({ video_url: 'https://example.com/clip.mp4', resolution: '2k' }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const task = await res.json();res = requests.post(
"https://videoupscaler.co/api/v1/upscale",
headers={
"Authorization": f"Bearer {os.environ['VIDEOUPSCALER_API_KEY']}",
"Idempotency-Key": "order-1234",
},
json={"video_url": "https://example.com/clip.mp4", "resolution": "2k"},
timeout=300,
)
res.raise_for_status()
task = res.json()Response
201 Created with the task:
{
"id": "0b6f3c1e-8a52-5d1e-9c43-3f1d2e7a9b10",
"status": "processing",
"resolution": "2k",
"video_url": "https://example.com/clip.mp4",
"duration_seconds": 12.4,
"processed_seconds": 12.4,
"credits_charged": 26,
"watermark": false,
"output_url": null,
"thumbnail_url": null,
"error": null,
"created_at": "2026-09-30T08:00:00.000Z",
"updated_at": "2026-09-30T08:00:02.000Z"
}Then poll the task until it is completed.
This request downloads the video before it answers, so a large remote file can take a while. Allow up to 5 minutes before timing out.
Idempotency
Send an Idempotency-Key with every request and reuse it when you retry after a timeout or network error:
- The same key with the same
video_urlandresolutionreturns the original task with200 OK. You are not charged again. - The same key with a different
video_urlorresolutionreturns409 idempotency_conflict. - Without the header, every request creates and charges a new task.
Billing
The price is the video's length in whole seconds (rounded up) times the rate for the resolution. See Credits & pricing. A 12.4-second video at 2K costs 13 × 2 = 26 credits.
If the job fails, the credits are refunded automatically and the task becomes failed.
Errors
| Status | Code | When |
|---|---|---|
400 | invalid_request | Missing or invalid video_url, resolution or Idempotency-Key |
401 | unauthorized | Missing or invalid API key |
403 | paid_plan_required | The account has never bought credits or subscribed |
402 | insufficient_credits | Your balance can't cover the video |
409 | idempotency_conflict | The Idempotency-Key was used with different parameters |
413 | file_too_large | The video is larger than 500MB |
422 | video_fetch_failed | We couldn't download video_url |
422 | unsupported_video | Not a readable MP4, MOV or WebM, or its duration is missing |
422 | video_too_long | The video is longer than 600 seconds |
422 | video_not_found | An uploaded video_url has no file (the upload didn't finish) |
504 | upstream_timeout | Downloading video_url took longer than 4 minutes |
See Errors for the error format.