Video Enhancement API Documentation
The Unblur Video API lets you enhance videos programmatically: upload a file (or point us at a URL), create an upscale job, poll until it completes, and download the result.
Base URL: https://unblurvideo.ai/api/v1
GET https://unblurvideo.ai/api/v1 is an unauthenticated discovery endpoint that returns the API version, documentation URL, OpenAPI description, and resource paths. All resource endpoints below require authentication.
Requests and responses are JSON unless a presigned upload URL explicitly asks for raw file bytes. JSON responses share one envelope:
{ "success": true, "data": { } }
Failed requests return success: false with an error message and a machine-readable code:
{ "success": false, "error": "Insufficient credits.", "code": "INSUFFICIENT_CREDITS" }
API access is included in the Pro plan. Requests from accounts without an active Pro subscription are rejected with
403 API_ACCESS_REQUIRED. See pricing.
Authentication
Create an API key in your dashboard, then pass it as a Bearer token on every request:
curl -H "Authorization: Bearer ubv_your_key_here" https://unblurvideo.ai/api/v1/credits
Keys start with ubv_ and are shown only once at creation time. Treat them like passwords: store them in your server's environment, never in client-side code or a repository. You can revoke a key at any time from the dashboard.
A missing or invalid key returns 401 with a standard WWW-Authenticate: Bearer ... challenge. API responses use Cache-Control: no-store and are marked X-Robots-Tag: noindex, nofollow; the human-readable page you are reading remains the canonical, indexable documentation.
Quick start
The fastest integration passes a public video URL — we download it, analyze it, and it's ready for a job in one call:
curl -X POST -H "Authorization: Bearer ubv_..." -H "Content-Type: application/json" -d '{"mode": "source_url", "source_url": "https://example.com/video.mp4"}' https://unblurvideo.ai/api/v1/uploads
Then create a job with the returned uploadId, and poll it until status is completed:
curl -X POST -H "Authorization: Bearer ubv_..." -H "Idempotency-Key: 7f7f2f78-3cb4-4e2d-92e2-5d7dc7cf70f9" -H "Content-Type: application/json" -d '{"upload_id": "UPLOAD_ID", "target_resolution": "1080p"}' https://unblurvideo.ai/api/v1/jobs
curl -H "Authorization: Bearer ubv_..." https://unblurvideo.ai/api/v1/jobs/JOB_ID
Once the completed output has been persisted to our storage, the response includes a temporary outputUrl you can download the enhanced video from.
Endpoints
POST /uploads
Register a video to process. Two modes:
Mode 1 — source_url. We fetch a public https URL server-side (max 500 MB; mp4, mov or webm). Metadata is extracted before the call returns, so you can create a job immediately.
curl -X POST -H "Authorization: Bearer ubv_..." -H "Content-Type: application/json" -d '{"mode": "source_url", "source_url": "https://example.com/video.mp4"}' https://unblurvideo.ai/api/v1/uploads
{
"success": true,
"data": {
"uploadId": "0c9d…",
"status": "uploaded",
"sizeBytes": 10485760,
"contentType": "video/mp4",
"metadata": { "resolution": "720p", "width": 1280, "height": 720, "duration": 12.4, "fps": 30 },
"metadataStatus": "ready"
}
}
Mode 2 — presigned upload. For local files: request an upload URL, PUT the file to it, then confirm.
curl -X POST -H "Authorization: Bearer ubv_..." -H "Content-Type: application/json" -d '{"filename": "clip.mp4", "content_type": "video/mp4", "size_bytes": 10485760}' https://unblurvideo.ai/api/v1/uploads
The response contains uploadId and a temporary uploadUrl (valid 10 minutes). Upload the raw file bytes to it:
{
"success": true,
"data": {
"uploadId": "0c9d…",
"uploadUrl": "https://…",
"expiresInSeconds": 600
}
}
curl -X PUT -H "Content-Type: video/mp4" --data-binary @clip.mp4 "UPLOAD_URL"
POST /uploads/{id}/confirm
Confirm a presigned upload after the PUT succeeds. Verifies the stored file and extracts video metadata synchronously (this call can take a few seconds).
curl -X POST -H "Authorization: Bearer ubv_..." https://unblurvideo.ai/api/v1/uploads/UPLOAD_ID/confirm
The response uses the same public upload fields as source_url mode:
{
"success": true,
"data": {
"uploadId": "0c9d…",
"status": "uploaded",
"sizeBytes": 10485760,
"metadata": { "resolution": "720p", "width": 1280, "height": 720, "duration": 12.4, "fps": 30 },
"metadataStatus": "ready"
}
}
If metadataStatus is "failed", the video could not be analyzed — try a different file.
POST /jobs
Create an enhancement job for an uploaded video.
| Field | Type | Description |
|---|---|---|
upload_id | string | Required. The uploadId from an upload call. |
target_resolution | string | Required. 1080p, 2k or 4k. |
preview | boolean | Optional. true renders a free ~3-second watermarked preview. |
curl -X POST \
-H "Authorization: Bearer ubv_..." \
-H "Idempotency-Key: 7f7f2f78-3cb4-4e2d-92e2-5d7dc7cf70f9" \
-H "Content-Type: application/json" \
-d '{"upload_id": "UPLOAD_ID", "target_resolution": "1080p"}' \
https://unblurvideo.ai/api/v1/jobs
Returns 201 with the job, including its creditCost. Credits are deducted when the job is created and automatically refunded if processing fails. If an identical job is already active, the existing job is returned with 200 and "duplicate": true — no double charge.
If metadata extraction is still in progress you get 409 METADATA_PENDING with a Retry-After header; retry after that many seconds.
Idempotent retries
Send a unique Idempotency-Key header when creating a job (1–128 characters; a UUID is a good choice). The key is scoped to your account:
- Repeating the same key with the same request returns the original job with
200and"duplicate": true, even if the first HTTP response was lost. - Reusing that key with different
upload_id,target_resolution, orpreviewvalues returns409 IDEMPOTENCY_KEY_REUSED. - The raw key is never stored; the server stores SHA-256 hashes of the key and normalized request.
Idempotency-Key is optional for backward compatibility, but strongly recommended for every production integration.
GET /jobs/{id}
Poll a job. Polling this endpoint also drives processing bookkeeping, so poll it (every 5–10 seconds is plenty) rather than scraping other state.
curl -H "Authorization: Bearer ubv_..." https://unblurvideo.ai/api/v1/jobs/JOB_ID
Key response fields:
| Field | Description |
|---|---|
status | pending, in_queue, processing, completed, failed or cancelled. |
outputUrl | Present after the completed output is persisted: a temporary download URL (valid 4 hours). Re-poll for a fresh one. Provider URLs are never exposed. |
creditCost | Credits charged for this job. |
errorMessage | Present when failed. Failed jobs are refunded automatically. |
The public response intentionally excludes internal R2 object keys, RunPod identifiers, provider payloads, and worker metadata.
GET /jobs
List your jobs, newest first. Query parameters: limit (default 20, max 100), offset, and optional status filter.
curl -H "Authorization: Bearer ubv_..." "https://unblurvideo.ai/api/v1/jobs?status=completed&limit=10"
GET /credits
Check your credit balance and plan.
curl -H "Authorization: Bearer ubv_..." https://unblurvideo.ai/api/v1/credits
{
"success": true,
"data": {
"totalCredits": 3600,
"subscriptionCredits": 3400,
"oneTimeCredits": 200,
"planName": "Pro",
"subscriptionStatus": "active",
"currentPeriodEnd": "2026-08-01T00:00:00.000Z"
}
}
Credits and pricing
Jobs are billed in credits based on video duration and target resolution:
| Target resolution | Credits per second of video |
|---|---|
| 1080p | 1.0 |
| 2k | 1.6 |
| 4k | 3.23 |
The total cost is first calculated from the full video duration and exact rate to two decimal places, then rounded up once to the next whole credit for both display and charging. For example, 10 seconds at the 4k rate is 32.30 credits, charged as 33. Previews are free. Source videos may be at most 1080p and 500 MB. Credits come with your subscription and can be topped up with one-time packs from the pricing page.
Rate limits
Limits apply per account (all keys share them). When exceeded, requests return 429 with a Retry-After header.
| Scope | Limit |
|---|---|
| Uploads (presigned or confirm) | 30 / hour each |
| Uploads via source_url | 10 / hour |
| Job creation | 45 / hour |
| Polling (job status, job list, credits) | 120 / minute |
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | INVALID_JSON, INVALID_INPUT, INVALID_IDEMPOTENCY_KEY, INVALID_SOURCE_URL, UNSUPPORTED_TARGET_RESOLUTION | The request syntax, fields, ID, query, URL, target resolution, or idempotency header is invalid. |
| 400 | UNSUPPORTED_FILE_EXTENSION, UNSUPPORTED_FILE_TYPE, FILE_TYPE_EXTENSION_MISMATCH | The declared filename and MIME type are unsupported or do not match. |
| 400 | FILE_SIZE_REQUIRED, FILE_SIZE_UNVERIFIED, FILE_SIZE_MISMATCH, FILE_TYPE_UNVERIFIED, FILE_TYPE_MISMATCH, FILE_NOT_FOUND, INVALID_OBJECT_KEY, UPLOAD_NOT_READY | The stored upload could not be verified or is not ready for processing. |
| 401 | MISSING_API_KEY, INVALID_API_KEY, KEY_EXPIRED | Missing, wrong or revoked key. |
| 402 | INSUFFICIENT_CREDITS | Not enough credits for this job. |
| 403 | API_ACCESS_REQUIRED, USER_BANNED | Your plan does not include API access or the account is unavailable. |
| 404 | UPLOAD_NOT_FOUND, JOB_NOT_FOUND, USAGE_NOT_FOUND | Resource or billing record does not exist or is not yours. |
| 409 | METADATA_PENDING | Video still being analyzed; retry after Retry-After seconds. |
| 409 | UPLOAD_STATUS_CONFLICT, JOB_ALREADY_CREATING, IDEMPOTENCY_KEY_REUSED | State or idempotency conflict. |
| 413 | FILE_TOO_LARGE | Video exceeds 500 MB. |
| 422 | METADATA_UNAVAILABLE, RESOLUTION_TOO_HIGH | The source video cannot be analyzed or exceeds the supported input resolution. |
| 422 | SOURCE_URL_FORBIDDEN, SOURCE_URL_UNRESOLVABLE, SOURCE_URL_TOO_MANY_REDIRECTS | The remote URL is unsafe, cannot resolve, or redirects too many times. |
| 429 | RATE_LIMIT_EXCEEDED | Slow down; respect Retry-After. |
| 500 / 503 | INTERNAL_SERVER_ERROR, RUNPOD_FAILED, IDEMPOTENCY_STORAGE_FAILED, SERVER_MISCONFIGURED, SERVICE_UNAVAILABLE | Temporary server or processing backend failure; charged credits are refunded when applicable. |
| 502 / 504 | SOURCE_FETCH_FAILED, SOURCE_FETCH_TIMEOUT | We couldn't download your source_url. |