PRODUCTION
The Try it consoles send real requests to your Base URL with Authorization: Bearer <key>. Keys and requests never leave your browser. Your API host must allow CORS from wherever these docs are served.
REST · JSON · v1

Build with the VideoForce API

Upload source videos, then generate winning ads and highlight clips from them — programmatically. Generation is asynchronous: you submit a job, poll it, and fetch the result. Every endpoint below is testable right here in your browser.

Overview

The VideoForce API is organized around REST. It has resource-oriented URLs, accepts and returns JSON, and uses standard HTTP verbs and status codes. Errors are returned as RFC 9457 application/problem+json.

ProtocolHTTPS only · JSON request and response bodies Base path/public/v1 — e.g. https://api.videoforce.ai/public/v1/projects AuthAuthorization: Bearer <api-key> on every request AsyncAd & highlight generation return a job you poll, then fetch its result
New here? Jump to the Quickstart for a complete upload → generate → download walkthrough, or read Job lifecycle to understand the async model first.

Authentication

Authenticate by sending your secret API key as a Bearer token on every /public/v1 request. Keep keys server-side; never ship them in browser or mobile client code.

HeaderValue
AuthorizationBearer vf_live_…
Content-Typeapplication/json (on requests with a body)

Getting a key

API keys are created and managed in the VideoForce dashboard under Organization → API Management (organization owner or admin only). A key belongs to your organization and is scoped to one workspace.

A newly created or rotated key shows its secret (vf_live_…) exactly once. Copy it immediately and store it in a secret manager — it cannot be retrieved again. Rotating a key revokes the previous one immediately.

Failed authentication

A missing, malformed, revoked, or expired key returns 401 with a uniform "Invalid API key" body — the API does not reveal which of those it was.

Quickstart

End to end in five steps: set your key, upload a source video, confirm it's ready, generate highlights, and generate an ad. All examples use production.

1 · Set your API key

Store the key in an environment variable so it never lands in source control.

shell
# macOS / Linux
export VIDEOFORCE_API_KEY="vf_live_your_key_here"

# Windows PowerShell (current session)
$env:VIDEOFORCE_API_KEY = "vf_live_your_key_here"

2 · Upload a source video

Uploading is three moves: create the record and get a short-lived signed URL, PUT the raw bytes to that URL (echoing every header in upload_headers verbatim), then poll the video until its status is ready.

upload_video.py
import os, time, requests

BASE = "https://api.videoforce.ai/public/v1"
KEY  = os.environ["VIDEOFORCE_API_KEY"]
auth = {"Authorization": f"Bearer {KEY}"}
PROJECT_ID = "<your-project-id>"
PATH = "/path/to/video.mp4"

# 1. create the record + get a signed upload URL
r = requests.post(f"{BASE}/videos/upload", headers=auth, json={
    "filename": os.path.basename(PATH),
    "content_type": "video/mp4",
    "size_bytes": os.path.getsize(PATH),
    "project_id": PROJECT_ID,
})
r.raise_for_status()
v = r.json(); video_id = v["video_id"]

# 2. PUT the bytes straight to storage — send upload_headers verbatim
with open(PATH, "rb") as f:
    requests.put(v["upload_url"], headers=v["upload_headers"], data=f).raise_for_status()

# 3. poll until ready: uploading -> processing -> ready
while True:
    d = requests.get(f"{BASE}/videos/{video_id}", headers=auth).json()
    print("status:", d["status"])
    if d["status"] in ("ready", "failed"): break
    time.sleep(10)
print(video_id, d["status"])
Save the video_id — you'll pass it to highlights and ads. A video must be ready before you can generate from it.

3 · Generate highlights, then poll & fetch

Trigger extraction, then follow the async pattern: poll the job to a terminal status, then fetch its result. See Job lifecycle for the full model.

curl
# submit — returns 202 with a job_id + poll_url
curl "$BASE/highlights" \
  -H "Authorization: Bearer $VIDEOFORCE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"project_id":"<project>","video_id":"<video>"}'

# poll until status is terminal, then fetch the result
curl "$BASE/jobs/<job_id>"        -H "Authorization: Bearer $VIDEOFORCE_API_KEY"
curl "$BASE/jobs/<job_id>/result" -H "Authorization: Bearer $VIDEOFORCE_API_KEY"

4 · Generate a winning ad

Send an inline brief (all seven fields required, non-empty) plus one or more source video_ids. Same async flow — poll the returned job, then fetch its result. See POST /ads for the full field reference.

Environments

Point requests at the host for your environment. The base path /public/v1 is identical across all of them.

EnvironmentBase URLStatus
Productionhttps://api.videoforce.ai/public/v1available
Staginghttps://api.stg.videoforce.ai/public/v1available
Developmenthttps://api.dev.videoforce.ai/public/v1available
Requesting the bare base path (/public/v1) returns a 404 by design — it isn't an endpoint. To check connectivity, call GET /public/v1/ping, which returns {"status":"ok"} without authentication.

Job lifecycle

Ad and highlight generation are long-running, so they're asynchronous. The trigger call doesn't return media — it returns a job handle you poll until it reaches a terminal state, then you fetch the result.

1
SubmitPOST /ads or POST /highlights202 with job_id, status: "queued", and poll_url.
2
PollGET /jobs/{job_id} until status is terminal. Non-terminal responses carry a Retry-After header (seconds) — use it as your backoff.
3
Fetch resultGET /jobs/{job_id}/result returns the produced resources with fresh signed download URLs. Call it again anytime to mint new URLs.

Job statuses

StatusTerminal?Meaning
queuednoAccepted, waiting for a worker.
processingnoActively generating.
succeededyesDone — /result returns the produced resources.
succeeded_emptyyesCompleted but produced no output (e.g. no highlights found). /result returns an empty resources array.
failedyesGeneration errored — see the job's error object.
abortedyesStopped before starting (e.g. insufficient credits) — never billed.

Poll until status is one of succeeded, succeeded_empty, failed, or aborted — these are final and will not change.

A complete poll loop

poll.py
import time, requests

def run_and_wait(base, key, path, body):
    auth = {"Authorization": f"Bearer {key}"}
    # submit
    job = requests.post(f"{base}{path}", headers={**auth,
        "Idempotency-Key": __import__("uuid").uuid4().hex}, json=body).json()
    job_id = job["job_id"]
    # poll with backoff, honoring Retry-After
    delay = 3
    while True:
        r = requests.get(f"{base}/jobs/{job_id}", headers=auth)
        d = r.json()
        if d["status"] in {"succeeded","succeeded_empty","failed","aborted"}: break
        time.sleep(int(r.headers.get("Retry-After", delay)))
        delay = min(delay * 2, 30)
    if d["status"] == "failed":
        raise RuntimeError(d["error"])
    if d["status"] == "succeeded":
        return requests.get(f"{base}/jobs/{job_id}/result", headers=auth).json()
    return {"resources": []}   # succeeded_empty / aborted
Result download URLs are short-lived signed links (~24h, see expires_at). Don't store the URL — store the resource_id/media_file_id and re-call /result to mint fresh links. Webhook callbacks (to avoid polling) are planned but not available in v1.

Rate limits

Generation jobs are limited per organization across three windows. Exceeding one returns 429 with IETF RateLimit policy headers.

WindowLimitRetry-After
Concurrent jobs (queued + processing)5— (slots free as jobs finish)
Jobs per hour303600
Jobs per UTC day150seconds to midnight
HeaderDescription
RateLimit-PolicyAll policies, e.g. "concurrency";q=5, "hourly";q=30;w=3600, "daily";q=150;w=86400.
RateLimitThe blocked policy with remaining r=0.
Retry-AfterSeconds until the window resets (hourly/daily blocks only; absent for concurrency).

Errors

Standard HTTP status codes. Every failure is RFC 9457 application/problem+json. The machine-readable identifier is the type URL's slug; include request_id when contacting support.

problem+json
{
  "type": "https://videoforce.ai/problems/video-not-found",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "One or more input videos do not exist in the selected workspace.",
  "instance": "/public/v1/highlights",
  "request_id": "…#123",
  "context": { "invalid_video_ids": ["…"] }
}

Validation errors (422) additionally carry an errors array of { field, message }.

StatusWhen it happens
200 / 201 / 202 / 204Success. 202 = async job accepted; 204 = no content.
400Missing/invalid Idempotency-Key on a generation call.
401Missing, malformed, revoked, or expired API key (Invalid API key).
402Insufficient credits — the job is aborted and never billed.
404Resource not found or not in your workspace (indistinguishable, by design).
409Job not finished yet or failed/aborted (on /result), highlights already generated for that video, or an idempotent retry (same key + body) is still in flight.
422Request validation failed, or an Idempotency-Key was reused with a different body.
429An organization rate-limit window is exhausted — see Rate limits.
503A dependency is temporarily unavailable — retry after Retry-After seconds.

Idempotency

Generation calls (POST /ads, POST /highlights) require an Idempotency-Key header — a unique value (e.g. a UUID) per logical request. It makes network retries safe and prevents duplicate jobs and double-billing.

CaseResult
Header missing / empty / > 255 chars400 idempotency-key-required
Same key + same body — original still running409 idempotency-key-in-flight — back off and re-poll the job
Same key + same body — original finishedReplays the original job (header Idempotent-Replayed: true) — no duplicate created or billed
Same key + different body422 idempotency-body-mismatch
Generate one key per logical request (not per HTTP attempt). Reuse the same key when retrying that request so you never trigger the job twice.

Pagination

List endpoints paginate. Two styles, depending on the resource.

StyleUsed byHow
Cursorprojects, ads, highlightsPass limit (1–100). Read has_more and next_cursor; pass next_cursor back as cursor for the next page.
PagevideosPass page (≥1) and page_size (1–100). Response includes total, page, page_size.

Versioning

The API is versioned in the URL path (/public/v1). Backwards-incompatible changes ship under a new path segment (/public/v2); additive changes (new endpoints, new optional fields, new enum values) can appear within v1 without notice, so write tolerant clients that ignore unknown fields.

Changelog

Notable changes, newest first.

DateChange
2026-08Added Production environment (https://api.videoforce.ai) is now publicly available.
2026-07Added Per-organization concurrent-job limit raised from 2 to 5.
2026-07Added Public API v1: projects, video upload, ad & highlight generation, async jobs, and result retrieval.

Support

Need help integrating?

  • Email support@videoforce.ai — include the request_id from any error response so we can trace it.
  • Manage keys and workspaces in the VideoForce dashboard.