API Reference

VIDing.AI REST API v2.1 — Selective Video Encoding

On This Page

Authentication

VIDing.AI supports two authentication methods:

1. API Key (Bearer Token)

For programmatic access, pass your API key in the Authorization header. API keys start with vd_live_ for production or vd_test_ for sandbox.

$ curl https://api.viding.ai/api/v1/jobs \
  -H "Authorization: Bearer vd_live_abc123..."

2. Session Cookie (Browser)

For browser-based access, authenticate via POST /login. The server sets a session cookie that is sent automatically on subsequent requests.

$ curl -X POST https://viding.ai/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "password=YOUR_PASSWORD" \
  -c cookies.txt

# Use the session cookie for subsequent requests
$ curl https://viding.ai/api/compress -b cookies.txt

Store your API key securely. The full key is only shown once at creation time. Revoked keys are rejected instantly.

Base URL

All API requests are made to:

https://api.viding.ai

For self-hosted or development, replace with your server URL. All endpoints accept and return application/json unless noted otherwise.

Upload Video

POST /api/v1/uploads/init Initialize chunked upload

Start a multipart upload. The API returns presigned URLs for each chunk. Maximum file size: 10 GB.

ParameterTypeDescription
filename requiredstringOriginal filename with extension
mime_type requiredstringvideo/mp4, video/quicktime, video/webm, video/x-matroska, video/x-msvideo
file_size requiredintegerTotal file size in bytes (max 10,737,418,240)

Response 200

{
  "upload_id": "a1b2c3d4-e5f6-...",
  "chunk_count": 50,
  "chunk_size": 10485760,
  "chunk_urls": ["https://storage.viding.ai/...", ...],
  "expires_at": "2026-04-01T12:00:00Z"
}
PUT /api/v1/uploads/{upload_id}/parts/{part_number} Upload a chunk

Upload each chunk to its presigned URL. Send the raw binary data as the request body with Content-Type: application/octet-stream.

ParameterTypeDescription
upload_id requiredstring (path)Upload ID from init response
part_number requiredinteger (path)1-indexed chunk number
Content-MD5 optionalstring (header)Base64-encoded MD5 for integrity check
POST /api/v1/uploads/{upload_id}/complete Complete multipart upload

Finalize the upload after all chunks are uploaded. Returns a file_id used to create compression jobs.

Response 200

{
  "file_id": "f1a2b3c4-...",
  "filename": "interview.mp4",
  "file_size": 524288000,
  "status": "ready"
}

For files under 100 MB, you can use the single-file upload endpoint instead:

POST /api/compress Upload and compress in one step

Upload a video file directly via multipart/form-data. The server compresses it immediately and returns the result. Best for files under 100 MB.

ParameterTypeDescription
video requiredfileVideo file (MP4, MOV, MKV, WebM, AVI)
target_ratio optionalnumberTarget compression ratio 0.1–0.9 (default: 0.3)

Create Compression Job

POST /api/v1/jobs Create a compression job

Submit a video for compression using VIDing.AI's selective encoding engine. The job runs asynchronously — poll the status endpoint or register a webhook.

ParameterTypeDescription
file_id requiredstringUUID from completed upload
codec optionalstringh264 | h265 | av1 — default: h265
preset optionalstringNamed preset: fast, balanced, quality (overrides codec/crf/speed)
crf optionalintegerConstant rate factor 0–63 (default: 23)
target_vmaf optionalnumberTarget VMAF score 80–100
target_ratio optionalnumberTarget compression ratio 0.1–0.9
webhook_url optionalstringURL to receive job completion event

Response 201

{
  "id": "job-uuid-...",
  "status": "queued",
  "codec": "h265",
  "preset": "balanced",
  "created_at": "2026-04-01T10:30:00Z"
}

Check Job Status

GET /api/v1/jobs/{job_id} Get job details and status

Returns the current status and details of a compression job.

ParameterTypeDescription
job_id requiredstring (path)Job UUID

Response 200

{
  "id": "job-uuid-...",
  "status": "complete",        // queued | processing | complete | failed
  "progress": 100,
  "input_size": 524288000,
  "output_size": 157286400,
  "compression_ratio": 0.30,
  "codec": "h265",
  "duration_seconds": 42.7,
  "created_at": "2026-04-01T10:30:00Z",
  "completed_at": "2026-04-01T10:30:42Z"
}

Status transitions: queuedprocessingcomplete | failed

GET /api/v1/jobs/{job_id}/metrics Get quality metrics (VMAF, PSNR, SSIM)

Returns perceptual quality metrics for a completed job.

Response 200

{
  "vmaf": 96.4,
  "psnr": 42.1,
  "ssim": 0.991,
  "bitrate_kbps": 2400,
  "frames_analyzed": 7200,
  "selective_ratio": 0.68  // fraction of frames using selective encoding
}
GET /api/processing_status Poll current processing status

Lightweight status polling endpoint. Returns the current state of the active processing job for the authenticated user.

Response 200

{
  "status": "processing",    // idle | processing | complete
  "message": "Processing video... This may take a few minutes.",
  "elapsed_seconds": 18.3
}

Download Result

GET /api/v1/jobs/{job_id}/download Get presigned download URL

Returns a time-limited presigned URL for downloading the compressed video. URLs expire after 60 minutes.

Response 200

{
  "download_url": "https://storage.viding.ai/outputs/...",
  "filename": "interview_compressed.mp4",
  "file_size": 157286400,
  "expires_at": "2026-04-01T11:30:00Z"
}

Validate Encoding

POST /api/validate Run encoding validation

Validate one or more video files. Returns codec details, bitrate analysis, and encoding quality assessment. Supports batch processing.

ParameterTypeDescription
filenamestringSingle video filename
filenamesarrayBatch mode: list of filenames
generate_pdf optionalbooleanGenerate a PDF report (default: false)
generate_csv optionalbooleanGenerate a CSV report (default: false)

Requires Authorization: Bearer header.

Webhooks

Register webhook endpoints to receive real-time notifications when jobs complete, fail, or other events occur. All webhook payloads are signed with HMAC-SHA256.

POST /api/webhooks/register Register a webhook endpoint
ParameterTypeDescription
url requiredstringHTTPS URL to receive events
events requiredarrayEvent types to subscribe to (see below)
secret optionalstringShared secret for HMAC-SHA256 signature verification

Available events

  • video.compressed — Compression job completed successfully
  • video.processed — Video processing completed
  • video.failed — Job failed with error
  • validation.complete — Encoding validation finished

Webhook Payload Schemas

video.compressed

Sent when a compression job completes successfully.

{
  "event": "video.compressed",
  "timestamp": "2026-04-01T10:30:42Z",
  "data": {
    "job_id": "job-uuid-...",
    "status": "complete",
    "input_file": "interview.mp4",
    "input_size": 524288000,
    "output_size": 157286400,
    "compression_ratio": 0.30,
    "codec": "h265",
    "duration_seconds": 42.7,
    "metrics": {
      "vmaf": 96.4,
      "psnr": 42.1,
      "ssim": 0.991
    },
    "download_url": "https://storage.viding.ai/outputs/...",
    "download_expires_at": "2026-04-01T11:30:42Z"
  }
}

video.failed

Sent when a compression job fails.

{
  "event": "video.failed",
  "timestamp": "2026-04-01T10:31:05Z",
  "data": {
    "job_id": "job-uuid-...",
    "status": "failed",
    "error_code": "PROC_008",
    "error_message": "Compression failed",
    "input_file": "corrupt-video.mp4"
  }
}

Verifying Webhook Signatures

If you provide a secret when registering, each webhook request includes an X-VIDing-Signature header containing an HMAC-SHA256 hex digest of the request body.

# Python verification
import hmac, hashlib

def verify_webhook(payload_bytes, signature, secret):
    expected = hmac.new(
        secret.encode(), payload_bytes, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Rate Limits

Rate limits are applied per API key. When exceeded, the API returns 429 Too Many Requests with a Retry-After header.

Tier Price Requests/min Max Upload Concurrent Jobs Monthly Bandwidth
Free $0/mo 10 RPM 500 MB 1 5 GB
Pro $49/mo 60 RPM 5 GB 5 500 GB
Enterprise Custom 600 RPM 10 GB Unlimited Unlimited

Rate limit headers are included in every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

Error Codes

All error responses include a JSON body with error_code, message, and HTTP status. Use the error_code for programmatic error handling.

HTTP Status Codes

200
Success
201
Created
204
Deleted (no content)
400
Bad request — invalid parameters
401
Unauthorized — missing or invalid credentials
403
Forbidden — NDA or permission required
404
Resource not found
408
Request timeout — processing took too long
409
Conflict — processing already in progress
413
Payload too large — file exceeds size limit
422
Unprocessable — video codec or format issue
429
Rate limit exceeded
500
Internal server error
503
Service unavailable
507
Insufficient storage

Error Code Reference

All API errors return a structured JSON body:

{
  "error_code": "VIDEO_007",
  "message": "File too large",
  "status": 413
}
VIDEO_001400
Unsupported video format
VIDEO_002404
Video file not found
VIDEO_003400
Video file empty or corrupted
VIDEO_004422
Cannot open video (codec issue)
VIDEO_005422
No readable frames
VIDEO_006422
Video too short
VIDEO_007413
File too large
VIDEO_008400
No video file provided
VIDEO_009400
No file selected
VIDEO_010400
Invalid MP4 file
PROC_001500
Processing failed
PROC_002409
Processing already in progress
PROC_003408
Processing timed out
PROC_008500
Compression failed
AUTH_001401
Invalid password
AUTH_002401
Login required
AUTH_003403
NDA required
AUTH_004401
Missing auth token
AUTH_005401
Invalid auth token
SYS_003429
Rate limited
DB_001503
Database connection failed
PDF_001500
PDF generation failed

Full error codes list available at /api/docs/errors (JSON) and OpenAPI spec at /api/docs/spec.

Complete Upload → Compress → Download Flow

End-to-end code samples for the complete video compression workflow.

cURL
Python
Node.js
#!/bin/bash
# Complete upload → compress → download flow

API_KEY="vd_live_abc123..."
BASE="https://api.viding.ai"

# --- Option A: Single-file upload + compress (files under 100 MB) ---

$ curl -X POST "$BASE/api/compress" \
  -H "Authorization: Bearer $API_KEY" \
  -F "video=@interview.mp4" \
  -F "target_ratio=0.3"

# --- Option B: Chunked upload for large files ---

# Step 1: Initialize upload
$ curl -X POST "$BASE/api/v1/uploads/init" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "interview.mp4",
    "mime_type": "video/mp4",
    "file_size": 524288000
  }'

# Step 2: Upload chunks to presigned URLs (from init response)
$ curl -X PUT "PRESIGNED_URL_1" \
  --data-binary @chunk_001.bin \
  -H "Content-Type: application/octet-stream"

# Step 3: Complete upload
$ curl -X POST "$BASE/api/v1/uploads/UPLOAD_ID/complete" \
  -H "Authorization: Bearer $API_KEY"

# Step 4: Create compression job
$ curl -X POST "$BASE/api/v1/jobs" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file_id": "FILE_ID_FROM_COMPLETE",
    "preset": "balanced",
    "webhook_url": "https://your-server.com/webhook"
  }'

# Step 5: Poll status (or wait for webhook)
$ curl "$BASE/api/v1/jobs/JOB_ID" \
  -H "Authorization: Bearer $API_KEY"

# Step 6: Download compressed file
$ curl "$BASE/api/v1/jobs/JOB_ID/download" \
  -H "Authorization: Bearer $API_KEY"

# Response includes a presigned download_url — fetch it directly:
$ curl -o compressed.mp4 "DOWNLOAD_URL"
"""
VIDing.AI — Complete upload → compress → download flow (Python)
pip install requests
"""
import requests, time, os

API_KEY = os.environ["VIDING_API_KEY"]
BASE    = "https://api.viding.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# --- Option A: Single-file upload (under 100 MB) ---

def compress_simple(filepath, target_ratio=0.3):
    with open(filepath, "rb") as f:
        resp = requests.post(
            f"{BASE}/api/compress",
            headers=HEADERS,
            files={"video": f},
            data={"target_ratio": target_ratio},
        )
    resp.raise_for_status()
    return resp.json()

# --- Option B: Chunked upload for large files ---

def compress_large(filepath, preset="balanced"):
    file_size = os.path.getsize(filepath)
    filename  = os.path.basename(filepath)

    # Step 1: Initialize upload
    init = requests.post(f"{BASE}/api/v1/uploads/init", headers=HEADERS, json={
        "filename": filename,
        "mime_type": "video/mp4",
        "file_size": file_size,
    }).json()

    upload_id  = init["upload_id"]
    chunk_size = init["chunk_size"]
    chunk_urls = init["chunk_urls"]

    # Step 2: Upload chunks
    with open(filepath, "rb") as f:
        for i, url in enumerate(chunk_urls):
            chunk = f.read(chunk_size)
            requests.put(url, data=chunk,
                headers={"Content-Type": "application/octet-stream"})
            print(f"  Uploaded chunk {i+1}/{len(chunk_urls)}")

    # Step 3: Complete upload
    complete = requests.post(
        f"{BASE}/api/v1/uploads/{upload_id}/complete",
        headers=HEADERS,
    ).json()

    # Step 4: Create compression job
    job = requests.post(f"{BASE}/api/v1/jobs", headers=HEADERS, json={
        "file_id": complete["file_id"],
        "preset": preset,
    }).json()

    job_id = job["id"]
    print(f"Job created: {job_id}")

    # Step 5: Poll until complete
    while True:
        status = requests.get(
            f"{BASE}/api/v1/jobs/{job_id}", headers=HEADERS
        ).json()
        print(f"  Status: {status['status']} ({status.get('progress', 0)}%)")
        if status["status"] == "complete":
            break
        if status["status"] == "failed":
            raise Exception(f"Job failed: {status}")
        time.sleep(5)

    # Step 6: Download result
    dl = requests.get(
        f"{BASE}/api/v1/jobs/{job_id}/download", headers=HEADERS
    ).json()

    output_path = f"compressed_{filename}"
    with requests.get(dl["download_url"], stream=True) as r:
        with open(output_path, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)

    print(f"Downloaded: {output_path} ({dl['file_size']} bytes)")
    return dl

# Usage
compress_large("interview.mp4", preset="balanced")
/**
 * VIDing.AI — Complete upload → compress → download flow (Node.js)
 * npm install node-fetch
 */
import fs from "fs";
import fetch from "node-fetch";
import path from "path";

const API_KEY = process.env.VIDING_API_KEY;
const BASE    = "https://api.viding.ai";
const HEADERS = { "Authorization": `Bearer ${API_KEY}` };

async function compressVideo(filepath, preset = "balanced") {
  const fileSize = fs.statSync(filepath).size;
  const filename = path.basename(filepath);

  // Step 1: Initialize upload
  const init = await fetch(`${BASE}/api/v1/uploads/init`, {
    method: "POST",
    headers: { ...HEADERS, "Content-Type": "application/json" },
    body: JSON.stringify({
      filename, mime_type: "video/mp4", file_size: fileSize,
    }),
  }).then(r => r.json());

  const { upload_id, chunk_size, chunk_urls } = init;

  // Step 2: Upload chunks
  const fd = fs.openSync(filepath, "r");
  for (let i = 0; i < chunk_urls.length; i++) {
    const buf = Buffer.alloc(Math.min(chunk_size, fileSize - i * chunk_size));
    fs.readSync(fd, buf, 0, buf.length, i * chunk_size);
    await fetch(chunk_urls[i], {
      method: "PUT", body: buf,
      headers: { "Content-Type": "application/octet-stream" },
    });
    console.log(`  Uploaded chunk ${i + 1}/${chunk_urls.length}`);
  }
  fs.closeSync(fd);

  // Step 3: Complete upload
  const complete = await fetch(
    `${BASE}/api/v1/uploads/${upload_id}/complete`,
    { method: "POST", headers: HEADERS }
  ).then(r => r.json());

  // Step 4: Create compression job
  const job = await fetch(`${BASE}/api/v1/jobs`, {
    method: "POST",
    headers: { ...HEADERS, "Content-Type": "application/json" },
    body: JSON.stringify({ file_id: complete.file_id, preset }),
  }).then(r => r.json());

  console.log(`Job created: ${job.id}`);

  // Step 5: Poll until complete
  while (true) {
    const status = await fetch(
      `${BASE}/api/v1/jobs/${job.id}`, { headers: HEADERS }
    ).then(r => r.json());

    console.log(`  Status: ${status.status} (${status.progress || 0}%)`);
    if (status.status === "complete") break;
    if (status.status === "failed") throw new Error(`Job failed: ${JSON.stringify(status)}`);
    await new Promise(r => setTimeout(r, 5000));
  }

  // Step 6: Download result
  const dl = await fetch(
    `${BASE}/api/v1/jobs/${job.id}/download`, { headers: HEADERS }
  ).then(r => r.json());

  const res = await fetch(dl.download_url);
  const dest = fs.createWriteStream(`compressed_${filename}`);
  await new Promise((resolve, reject) => {
    res.body.pipe(dest);
    dest.on("finish", resolve);
    dest.on("error", reject);
  });

  console.log(`Downloaded: compressed_${filename} (${dl.file_size} bytes)`);
}

compressVideo("interview.mp4");

OpenAPI Specification

The full OpenAPI 3.0 spec is available as machine-readable JSON for code generation and API client tools:

OpenAPI Spec (JSON) Error Codes (JSON)

New to VIDing.AI? Start with the Quick Start Guide to compress your first video in under 5 minutes.