Knightvision Partner API Reference

Integrate AI-powered ad analysis, image generation, video generation, captions, voiceover (TTS), and the Script Runner pipeline into your own product. The Partner API gives you programmatic access to Knightvision's full feature set.

Requests and responses use JSON unless an endpoint explicitly documents multipart form data or a raw binary body. The API is versioned — the current version is v1.

Base URL: https://knightvision.ai

Get an API key in under a minute
Self-serve signup — verify your email and we'll show your key once. Pricing is the standard Knightvision rate + 10% (no monthly subscription required).
Already a partner? Sign in
We'll email you a magic-link — no API key paste required. Your dashboard shows usage and recent jobs, tops up credits via Stripe, and can issue a replacement API key. Lost your key? Sign in and generate a new one — we can't re-send the old one, it's only ever stored as a hash.

Authentication

All requests require an API key in the Authorization header:

Authorization: Bearer kv_partner_<your_api_key>
StatusResponse
401{"error": "Missing or invalid Authorization header"}
401{"error": "Invalid API key"}
403{"error": "API key deactivated"}
403HTML page containing error code: 1010: generic User-Agent, see below

Every successful response includes a unique request_id field for debugging.

Send a User-Agent header

Requests that carry a generic library default such as Python-urllib or an older Java client are rejected by our edge protection with HTTP 403 and an HTML page containing error code: 1010, before they reach the API. Identify your client with any custom value:

User-Agent: MyApp/1.0
curl, requests, httpx, axios and node fetch pass as they are. A 403 with error code: 1010 always means the User-Agent header is missing or generic, never that the API key is wrong.

Versioning

The API uses URL-based versioning (/api/v1/...). The current version is v1 and is considered stable. Breaking changes will only be introduced in a new version with advance notice. Non-breaking additions (new response fields, new optional parameters) may be added to v1 at any time.


Credits & Pricing

Your account has a credit pool. Each API call that performs work deducts credits. Check your balance anytime via GET /api/v1/partner/credits.

Pricing tiers

Every partner account has a pricing_tier, returned by GET /me and GET /credits:

  • api_plus10 — self-serve accounts. Every job is billed at the standard Knightvision rate + 10 %, rounded up to a whole credit per job (never per second or per scene). All prices on this page are api_plus10 prices. Per-second models show the rate × 1.1; the job total is rounded up once.
  • standard — legacy accounts billed at the standard basis, i.e. the numbers in the costs object of GET /credits.
  • custom — negotiated contract rates; your contract is the reference. Models without a negotiated rate on a custom account return 400 before any credits are charged.

Two places show the standard basis instead, because that is what the API itself returns: the costs object of GET /credits, and cost_per_scene in Script Runner responses. The authoritative amount for any job is the credits_used field of its submit response.

ActionCredits (api_plus10)
Ad Analysis55
Image Generation (per image, Nano Banana Pro 2K)17
Video: Kling V3 720p — 5s / 10s per clip55 / 110 · +AI sound 83 / 160
Video: Kling V3 1080p — 5s / 10s per clip72 / 165 · +AI sound 110 / 220
Video: Kling Motion Control (720p / 1080p)15.4/s / 20.9/s
Video: Seedance 2.0 (480p / 720p / 1080p / 4K)15.4 / 31.9 / 79.2 / 160.6 per s
Video: Seedance 2.5 (480p / 720p / 1080p)22 / 49.5 / 122.1 per s
Video: Veo 3.1 Quality (720p / 1080p / 4K)165 / 196 / 220 flat
Video: Veo 3.1 Fast (720p / 1080p / 4K)46 / 54 / 132 flat
Animation (per clip, 8s / 1080p / audio)172
Merge Videos (per merge)110
Captions (transcription + styled burn-in)28 per job (standard basis 25)
Omni Character (per character)33
Omni Video 1080p — 4s / 6s / 8s / 10s132 / 143 / 154 / 165
Omni Video 4K — 4s / 6s / 8s / 10s198 / 209 / 220 / 231
Omni Script Runner (per section, 1080p — 4s / 6s / 8s / 10s)132 / 143 / 154 / 154
Omni Fix Prompt (per rewrite)2
Script Runner (per scene, default model)55 standard basis (= cost_per_scene); + 10 % on the run total
Voiceover: v4 Turbo (eleven_v4_turbo, default)1 credit per 80 chars, + 10 % rounded up per job (min 2)
Voiceover: v4 (eleven_v4)1 credit per 40 chars, + 10 % rounded up per job (min 2)
Voiceover: Turbo v2.5 (eleven_turbo_v2_5)1 credit per 80 chars, + 10 % rounded up per job (min 2)
Voiceover: v3 (eleven_v3)1 credit per 50 chars, + 10 % rounded up per job (min 2)
Music (Suno V5 / V5.5) — generate or extend11 per job (standard basis 10)
Image Upscale (2K / 4K / 8K output)8 / 16 / 31
Video Upscale (1× / 2× / 4×, per input second)6.6 / 6.6 / 11 per s
Metadata Removal (per image or video)Free: 0 credits (daily quota)
If a video, image, voiceover, music, Caption job, or Script Runner scene fails on our side, credits are automatically refunded. A Caption job that finds no speech is also refunded; editing and re-burning an existing Caption job is free. The Omni Script Runner caps a 10 s section at the 8 s price (154), which is why it is cheaper than the same clip via POST /omni/video.

Buying Credits

Top up your partner credit balance any time via the self-serve billing portal — pay with card via Stripe, credits land instantly. Larger packs include bonus credits, and you can keep auto-refill on so your balance is topped up automatically when it runs low. We'll email you a magic-link to log in (no API key paste required); the link is valid for 15 minutes. Every top-up's Stripe invoice (and every auto-refill's receipt) is on your Invoices page, which has its own address you can bookmark or forward.

Need a custom volume, invoice billing, or a different pricing tier? Email [email protected].

Lost your API key?

Keys are stored as a hash only — we can never show you an existing key again, not even on request. Instead, issue a replacement yourself: sign in with your partner email (magic-link, no key required) and use Regenerate API key on your dashboard.

The replacement is shown exactly once, and the previous key stops authenticating immediately — there is no grace period. Update every deployment that uses the old key before you rotate.


Rate Limits

Rate limits are per API key, per endpoint.

EndpointLimit
POST /analyze20/min
GET /status/<job_id>60/min
GET /analyses20/min
GET /credits30/min
GET /me60/min
GET /usage20/min
GET /transactions20/min
POST /generate-video5/min
GET /video-status/<job_id>60/min
GET /videos20/min
GET /webhook60/min
POST /webhook10/min
POST /webhook/test5/min
GET /webhook/deliveries30/min
GET /captions/style-options60/min
POST /captions5/min
POST /captions/upload5/min
POST /captions/uploads/init10/min
POST /captions/uploads/<session_id>/part/<index>300/min and 3000/h
POST /captions/uploads/<session_id>/finalize10/min
POST /captions/uploads/<session_id>/abort60/min
GET /captions/<job_id>60/min
GET /captions20/min
GET /captions/<job_id>/chunks30/min
POST /captions/<job_id>/edit10/min
POST /generate-animation5/min
POST /merge-videos5/min
POST /omni/characters20/min
GET /omni/characters60/min
DELETE /omni/characters/<id>20/min
GET /omni/voice-presets60/min
POST /omni/voices20/min
GET /omni/voices60/min
DELETE /omni/voices/<id>20/min
POST /omni/video10/min
GET /omni/video/<job_id>60/min
GET /omni/videos20/min
POST /omni/script-runner5/min
GET /omni/script-runner/<batch_id>60/min
GET /omni/script-runner20/min
POST /omni/improve-prompt10/min
POST /generate-image10/min
GET /image-status/<gen_id>60/min
GET /images20/min
POST /upscale-image10/min
POST /upscale-video5/min
POST /remove-metadata20/min
POST /social-download10/min
GET /social-download/<download_id>120/min
GET /social-download/<download_id>/file/<idx>240/min
GET /social-downloads30/min
POST /script-runner5/min
GET /script-runner/<job_id>60/min
GET /script-runner20/min
POST /generate-voiceover20/min
GET /voiceover/voices60/min
GET /voiceovers30/min
POST /generate-music5/min
POST /extend-music5/min
GET /music-status/<job_id>60/min
GET /music20/min

When rate limited you receive HTTP 429. Wait at least 10 seconds before retrying.

These are the defaults for every account. If they are not enough for your workload, open a support ticket in the Knightvision Discord (or write to [email protected]) and tell us your account name and the throughput you need. We raise the in-flight job cap and the per-minute limits on POST /generate-video, POST /omni/video and POST /generate-animation for your account. There is no charge for higher limits.


Conventions

IDs: every job, batch, character, and merge id is kv- followed by 8 hex characters (for example kv-1a2b3c4d). Images additionally carry an integer generation_id, voiceover and music rows an integer id. Store the kv- id — it is what support and the admin tools look up.

Timestamps are returned in UTC, formatted as YYYY-MM-DD HH:MM:SS.

Generated media URLs: videos, images, music tracks and Omni videos created through the Partner API are kept and are not subject to the consumer cleanup window. Three outputs are different: Caption output has a normal cleanup window of 14 days (storage pressure can prune it earlier — once unavailable, captioned_url becomes null and media_pruned becomes true); merged clips from POST /merge-videos are kept for about 7 days; cleaned copies from POST /remove-metadata (images and videos) are kept for 7 days (their expires_at). Download them promptly.

Idempotency: Unless an endpoint says otherwise, submitting the same request twice creates two separate jobs and deducts credits twice. To avoid duplicate charges, check the response for a job_id before retrying. If your request timed out, check the corresponding list endpoint. Caption chunk-upload finalization and POST /extend-music are exceptions: music extension requires an Idempotency-Key header, returns the existing job for an identical retry, and rejects reuse with different parameters.

Async operations: Ad analysis, video generation, image generation, image and video upscaling, music generation and extension, Captions, and Script Runner are asynchronous. Submit a request, receive an ID, then poll the corresponding status endpoint. We recommend polling every 3-5 seconds.

Pagination: Unless an endpoint documents different bounds, list endpoints support ?limit=N (default 20, max 50) and ?offset=N (default 0). Responses include total, limit, and offset fields. Wider bounds: GET /script-runner and GET /omni/script-runner allow up to 100; GET /voiceovers and GET /music default to 30 and allow up to 200; GET /transactions defaults to 50 and allows up to 200. GET /omni/characters returns your whole library without pagination.

Ad Analysis

Submit a Facebook, Instagram, or TikTok ad URL and receive a full AI breakdown — summary, transcription, deep analysis, and a UGC-style script ready for reuse. See the public Ad Analyzer overview for the product workflow.

Submit Ad Analysis

POST /api/v1/partner/analyze
Async 50 credits

Analyze a Facebook or Instagram ad. Submit here, then poll /status/<job_id>.

Request

{
  "url": "https://www.facebook.com/ads/library/?id=123456789"
}
FieldTypeRequiredDescription
urlstringyesFacebook/Instagram ad URL or numeric ad ID
partner_job_idstringnoOptional partner-supplied cross-reference ID (e.g. lx-abc123), 1-64 chars, alphanumeric + -/_. Returned in subsequent GET responses.

Allowed domains: facebook.com, www.facebook.com, m.facebook.com, instagram.com, www.instagram.com, tiktok.com, www.tiktok.com, vm.tiktok.com

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-3f9a2c1e",
  "status": "queued",
  "credits_used": 50,
  "credits_remaining": 950
}

Errors

StatusError
400"Request body must be JSON"
400"Please provide a URL"
400"Please provide a valid Facebook, Instagram, or TikTok URL"
402"Insufficient credits"

Example

curl -X POST https://knightvision.ai/api/v1/partner/analyze \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.facebook.com/ads/library/?id=123456789"}'

Check Analysis Status

GET /api/v1/partner/status/<job_id>

Poll until status is complete or error.

Response — Processing (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-3f9a2c1e",
  "status": "downloading",
  "message": "Downloading ad from Facebook..."
}

Possible status values: queued, downloading, extracting_frames, transcribing, analyzing

Response — Complete (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-3f9a2c1e",
  "status": "complete",
  "result": {
    "summary": "Brief summary of the ad",
    "transcription": "Full audio transcription",
    "analysis": "Detailed AI analysis of the ad",
    "ugc_script": "Generated UGC script based on the ad"
  }
}

Errors

StatusError
400"Invalid job ID"
404"Not found" / "Job not found"

Example

curl https://knightvision.ai/api/v1/partner/status/kv-3f9a2c1e \
  -H "Authorization: Bearer kv_partner_your_key_here"

List Analyses

GET /api/v1/partner/analyses

List your submitted analyses with pagination.

Query Parameters

ParamTypeDefaultDescription
limitinteger20Max results (1-50)
offsetinteger0Skip first N results

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "analyses": [
    {
      "job_id": "kv-3f9a2c1e",
      "status": "complete",
      "credits_used": 50,
      "created_at": "2026-03-23 14:15:00",
      "result": { "summary": "...", "analysis": "...", "transcription": "...", "ugc_script": "..." }
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0
}

Example

curl "https://knightvision.ai/api/v1/partner/analyses?limit=10&offset=0" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Image Generation

Generate high-quality AI images. Four models are supported: Nano Banana Pro (default, photoreal & product mockups), GPT Image 2 (strong typography, infographics, stylised graphics) and the two GPT Image 2.5 variants — Flare (faster, the everyday choice) and Sunburst (more polished, for campaign assets). Both 2.5 variants cost the same, add transparent-background output and a much wider set of aspect ratios. Outputs can be used directly as start frames or reference images for Video Generation. See the public AI Image Generator overview for the product workflow.

Fallback behaviour: for Nano Banana Pro and GPT Image 2, a transient failure during polling is automatically retried on a secondary provider that preserves your reference images. The GPT Image 2.5 variants run on a single provider with no second tier. Either way you see one final result: a successful image OR a clean refund with a descriptive error. Provider names are never leaked in the response.

Generate Image

POST /api/v1/partner/generate-image
Async 17–38 credits per image (model + quality)

Generate AI images. Poll /image-status/<generation_id> for results.

Request

{
  "prompt": "A modern product mockup on a clean white background",
  "model": "nano-banana-pro",
  "aspect_ratio": "1:1",
  "quality": "2K",
  "quantity": 2
}
FieldTypeRequiredDefaultDescription
promptstringyes—Image description
modelstringnonano-banana-pronano-banana-pro, gpt-image-2, gpt-image-2-5-flare or gpt-image-2-5-sunburst
aspect_ratiostringno1:1Nano Banana Pro: 1:1, 9:16, 16:9, 3:4, 4:3. GPT Image 2: 1:1, 16:9, 9:16 only. GPT Image 2.5: auto (default), 1:1, 3:2, 2:3, 4:3, 3:4, 16:9, 9:16, 21:9, and 27:16, 16:27, 9:8, 8:9 — those last four support 1K only.
qualitystringnomodel-dependentNano Banana Pro: 2K (default) or 4K. GPT Image 2 and GPT Image 2.5: 1K (default), 2K, or 4K. Alias: resolution.
backgroundstringnoautoGPT Image 2.5 only: auto, transparent or opaque. At 2K/4K a transparent result also needs the prompt to describe an isolated subject with no backdrop, scenery or shadow (for image-to-image, ask explicitly to extract the subject and keep the background transparent). Ignored by the other models.
quantityintegerno1Number of images (1-4). Total cost = per-image cost × quantity.
ref_imagesarrayno—Up to 5 reference images as [{"base64": "...", "mime_type": "image/jpeg"}]. Each ~20 MB binary max, ~72 MB combined.
reference_image_urlsarrayno—Up to 5 public HTTPS image URLs used as references — an alternative to ref_images (a Knightvision image_url works). Combined with ref_images, only the first 5 references are used.
strip_metadatabooleannofalsetrue stores and serves the finished images without their AI-provenance metadata: the C2PA Content Credentials manifest plus EXIF, XMP and IPTC. Lossless: the pixels are untouched and invisible watermarks embedded in the pixels stay. Applies to every image of the request, at no extra cost. /image-status and /images report the outcome as metadata_removed. Any value other than true or false is a 400, before any charge. To clean an image you already have, use Remove Metadata.
partner_job_idstringno—Optional cross-reference ID (e.g. lx-abc123), see Submit Ad Analysis for format.

Per-image cost

ModelQualityCredits (api_plus10)
Nano Banana Pro2K17
Nano Banana Pro4K25
GPT Image 21K21
GPT Image 22K27
GPT Image 24K32
GPT Image 2.5 Flare1K25
GPT Image 2.5 Flare2K31
GPT Image 2.5 Flare4K38
GPT Image 2.5 Sunburst1K25
GPT Image 2.5 Sunburst2K31
GPT Image 2.5 Sunburst4K38

Standard basis: 15 / 22 (Nano Banana Pro 2K / 4K), 19 / 24 / 29 (GPT Image 2 1K / 2K / 4K) and 22 / 28 / 34 (either GPT Image 2.5 variant, 1K / 2K / 4K) — see GET /credits. The + 10 % is applied to the whole request (per-image cost × quantity) and rounded up once.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "generation_ids": [12345, 12346],
  "public_ids": ["kv-a1b2c3d4", "kv-e5f6a7b8"],
  "credits_used": 33,
  "credits_remaining": 917
}
For quantity: 1, the response also includes generation_id and public_id (singular) fields for convenience. The public_id (kv-<8hex>) and the integer generation_id are interchangeable — both can be passed to /image-status/<id>. The public_id matches the canonical KV identifier used for videos and admin links.

Errors

StatusError
400"Please provide a prompt"
400"Invalid model. Allowed: nano-banana-pro, gpt-image-2, gpt-image-2-5-flare, gpt-image-2-5-sunburst"
400"Invalid aspect_ratio. Allowed: 1:1, 9:16, 16:9, 3:4, 4:3" (or "Invalid aspect_ratio for gpt-image-2. Allowed: 1:1, 16:9, 9:16")
400"Aspect ratio 9:8 supports 1K only. Use 1K with 9:8, or pick another aspect ratio for 4K." (GPT Image 2.5, rejected before any charge)
400"gpt-image-2 cannot generate 4K at 1:1 aspect ratio. Use 4K with 16:9 or 9:16, or use 2K/1K with 1:1."
400"Invalid reference image URL: ..."
400"strip_metadata must be true or false"
413"ref_images[].base64 too large (max 20 MB each)" / "ref_images combined too large (max 72 MB total)"
402"Insufficient credits. Need N credits."
500"Image generation not available"

Example — Nano Banana Pro (default)

curl -X POST https://knightvision.ai/api/v1/partner/generate-image \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A modern product mockup on a clean white background", "quantity": 2}'

Example — GPT Image 2 (4K)

curl -X POST https://knightvision.ai/api/v1/partner/generate-image \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A bold typographic poster: SALE 50% OFF, neon gradient", "model": "gpt-image-2", "quality": "4K", "aspect_ratio": "9:16"}'

Check Image Status

GET /api/v1/partner/image-status/<generation_id>

Poll until status is success or failed. Credits are auto-refunded on failure. <generation_id> accepts either the integer generation_id or the public_id (kv-<8hex>).

Response — Success (200)

{
  "request_id": "a1b2c3d4e5f6",
  "status": "success",
  "generation_id": 12345,
  "image_url": "https://knightvision.tech/static/generated_images/gen_12345.png",
  "model": "Nano Banana Pro",
  "aspect_ratio": "1:1",
  "resolution": "2K",
  "prompt": "A modern product mockup on a clean white background",
  "metadata_removed": false
}

metadata_removed is true when the request asked for strip_metadata and the stored file carries none of that metadata; otherwise it is false.

A failed job returns "status": "failed" with a sanitized error string; provider names are never exposed.

Response — Pending (200)

{
  "request_id": "a1b2c3d4e5f6",
  "status": "pending",
  "generation_id": 12345
}

Errors

StatusError
404"Not found" (generation does not exist or not owned by your account)

Example

curl https://knightvision.ai/api/v1/partner/image-status/12345 \
  -H "Authorization: Bearer kv_partner_your_key_here"

List Images

GET /api/v1/partner/images

List your generated images, newest first.

Query Parameters

ParamTypeDefaultDescription
limitinteger20Max results (1-50)
offsetinteger0Skip first N results

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "images": [
    {
      "generation_id": 12345,
      "public_id": "kv-a1b2c3d4",
      "partner_job_id": "lx-img-001",
      "prompt": "A modern product mockup",
      "model_used": "nano-banana-pro",
      "aspect_ratio": "1:1",
      "resolution": "2K",
      "credits_used": 17,
      "status": "success",
      "image_url": "https://knightvision.tech/static/generated_images/gen_12345.png",
      "ref_image_urls": [],
      "metadata_removed": false,
      "created_at": "2026-04-05 14:15:00"
    }
  ],
  "total": 15,
  "limit": 20,
  "offset": 0
}

Example

curl "https://knightvision.ai/api/v1/partner/images?limit=10" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Upscaler

Enhance and enlarge existing images and videos with an AI upscaler: 1 (enhance only, same size), 2 or 4 times. Image output goes up to 8K on the long edge, video output up to 4K. Pass one of your own generated files (a Knightvision image_url / video_url) or any public HTTPS URL. Sources are never cropped or re-encoded — the file you send is the file that gets upscaled — and a video is downloaded and measured server-side before credits are charged.

Upscale Image

POST /api/v1/partner/upscale-image
Async 8–31 credits per image (output size)

Start an image upscale. Poll /image-status/<public_id> for the result, exactly like Generate Image. The upscaled image is a PNG.

Request

{
  "image_url": "https://knightvision.tech/static/generated_images/kv-a1b2c3d4.png",
  "upscale_factor": 2,
  "partner_job_id": "lx-abc123"
}
FieldTypeRequiredDefaultDescription
image_urlstringone of—Public HTTPS URL of a JPEG, PNG or WebP (max 10,000,000 bytes). A Knightvision image_url from Check Image Status or List Images is read from disk. Hostnames must be ASCII, no userinfo@host.
image_base64stringone of—The image as base64 (~20 MB payload cap, decoded image max 10,000,000 bytes). Stored byte-for-byte; the format is detected from the bytes.
upscale_factorintegerno21, 2 or 4. A factor whose output would exceed 8,192 px on the long edge is rejected with 400 before any credits are charged.
partner_job_idstringno—Optional cross-reference ID (e.g. lx-abc123), see Submit Ad Analysis for format.

Cost

MediaSizeCredits (api_plus10)
Image2K output (long edge ≤ 2,048 px)8
Image4K output (long edge ≤ 4,096 px)16
Image8K output (long edge ≤ 8,192 px)31
Video1× / 2× — per input second6.6
Video4× — per input second11

Standard basis: 7 / 14 / 28 per image (by output tier) and 6 / 6 / 10 per input second — see costs.upscale in GET /credits. Video is billed on whole input seconds, rounded up once; the + 10 % is applied to the job total and rounded up once.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "generation_id": 12345,
  "public_id": "kv-7f3a9c21",
  "job_id": "kv-2b9e4d10",
  "partner_job_id": "lx-abc123",
  "status": "pending",
  "upscale_factor": 2,
  "output": {"width": 2048, "height": 2048, "tier": "2K"},
  "credits_used": 8,
  "credits_remaining": 992
}
output.tier is the price tier the job was billed at. Poll /image-status/<public_id>; on failure the credits are refunded automatically.

Errors

StatusError
400"Provide exactly one of image_url or image_base64"
400"upscale_factor must be 1, 2 or 4"
400"Output would exceed 8K" (choose a smaller factor)
400"Unsupported image format. Please use JPEG, PNG or WebP."
413"image_base64 too large (max 20 MB)" / "Image must be under 10 MB."
402"Insufficient credits. Need N credits."
500"Upscaling not available"

Example

curl -X POST https://knightvision.ai/api/v1/partner/upscale-image \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://knightvision.tech/static/generated_images/kv-a1b2c3d4.png", "upscale_factor": 2}'

Upscale Video

POST /api/v1/partner/upscale-video
Async 6.6 or 11 credits per input second

Start a video upscale. The source is downloaded and measured first; you are charged for its whole input seconds at the rate of the chosen factor. Poll /video-status/<job_id> for the result, exactly like Generate Video. Expect several minutes.

Request

{
  "video_url": "https://knightvision.tech/static/generated_videos/kv-1a2b3c4d.mp4",
  "upscale_factor": 2,
  "partner_job_id": "lx-abc124"
}
FieldTypeRequiredDefaultDescription
video_urlstringyes—Public HTTPS URL of an MP4 or MOV, max 50,000,000 bytes and 120 seconds. A Knightvision video_url from Check Video Status or List Videos is read from disk. Larger or longer files are rejected before any credits are charged — nothing is re-encoded. Hostnames must be ASCII, no userinfo@host.
upscale_factorintegerno21, 2 or 4. A factor whose output would exceed 4,096 px on the long edge is rejected with 400 (a 1080p source can be upscaled 2×, not 4×).
partner_job_idstringno—Optional cross-reference ID, see Submit Ad Analysis for format.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-5c6d7e8f",
  "partner_job_id": "lx-abc124",
  "status": "queued",
  "upscale_factor": 2,
  "input_seconds": 8,
  "output": {"width": 2560, "height": 1440},
  "credits_used": 53,
  "credits_remaining": 939
}
input_seconds is what you were billed for (8 s × 6 = 48 standard, 53 on api_plus10). Poll /video-status/<job_id>; on failure the credits are refunded automatically.

Errors

StatusError
400"video_url is required" / "video_url must be a public https URL"
400"upscale_factor must be 1, 2 or 4"
400"Video must be 120 seconds or shorter."
400"Output would exceed 4K" (choose a smaller factor)
400"Video must be an MP4 or MOV file." / "A reference video could not be downloaded."
413"Video must be under 50 MB. Please upload a smaller file (videos are never re-encoded)."
402"Insufficient credits. Need N credits."
500"Upscaling not available"

Example

curl -X POST https://knightvision.ai/api/v1/partner/upscale-video \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"video_url": "https://knightvision.tech/static/generated_videos/kv-1a2b3c4d.mp4", "upscale_factor": 2}'

Metadata Removal

Remove the AI-provenance declaration from an image or video you already have: the C2PA Content Credentials manifest (what platforms read to show an “AI info” label) plus the EXIF (including GPS), XMP and IPTC metadata of an image, or the container and stream tags of a video. Free: 0 credits. A daily quota per account is the limit instead, counted separately for images and videos. For media you generate here, pass strip_metadata: true to Generate Image, Generate Video or Generate Omni Video instead and the finished file never carries it.

Remove Metadata

POST /api/v1/partner/remove-metadata
Free: 0 credits

Synchronous: the response carries the URL of the cleaned copy. The removal is lossless: the image data is copied byte for byte, so the pixels are identical. What stays: the colour profile, any data stored after the image itself (for example an HDR gain map) and invisible watermarks embedded in the pixels. Only the cleaned copy is stored, for 7 days; your original is never kept.

Videos (MP4 / MOV): send video_url instead. The video and audio streams are copied unchanged (no re-encode, same frames, same quality) into a new file without the C2PA manifest, container and stream tags (title, comment, encoder, location and the like), chapters and tracks that carry neither picture nor sound (timecode, subtitles, data). MOV stays MOV, everything else comes back as MP4, always with the index at the front for instant playback. Invisible watermarks embedded in the frames stay. Max 100 MB; the download must finish within 30 seconds.

Request

{
  "image_url": "https://example.com/campaign/hero.jpg"
}
{
  "video_url": "https://example.com/campaign/spot.mp4"
}
FieldTypeRequiredDefaultDescription
image_urlstringone of the three—Public HTTPS URL of a JPEG, PNG or WebP, max 20 MB. A Knightvision image_url (for example from List Images) is read from disk. Hostnames must be ASCII, no userinfo@host.
image_base64stringone of the three—The image file, base64-encoded (max 20 MB of image data).
video_urlstringone of the three—Public HTTPS URL of an MP4 or MOV video, max 100 MB. A Knightvision video_url (for example from List Videos or Check Omni Video Status) is read from disk. Videos are accepted by URL only. Send exactly one of image_url / image_base64 / video_url.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "image_url": "https://knightvision.tech/static/clean_images/3f9c0a7d5e2b4c1f8a6d0e9b7c5a3f21.jpg",
  "format": "jpeg",
  "content_type": "image/jpeg",
  "removed": ["c2pa", "exif", "xmp"],
  "c2pa_found": true,
  "bytes_in": 2481734,
  "bytes_out": 2474302,
  "expires_at": "2026-10-05 14:15:00"
}

Response for a video (200)

{
  "request_id": "a1b2c3d4e5f6",
  "video_url": "https://knightvision.tech/static/clean_videos/9b1e4c7a2d5f8e3b6a0c9d4f7e2b5a18.mp4",
  "format": "mp4",
  "content_type": "video/mp4",
  "removed": ["c2pa", "other"],
  "c2pa_found": true,
  "bytes_in": 7397371,
  "bytes_out": 7383110,
  "expires_at": "2026-10-09 14:15:00"
}
removed lists what was found and removed, as categories: c2pa, exif, xmp, iptc, other (videos report c2pa, xmp and other; tags, chapters and extra tracks count as other). An empty list means the file carried none of it (you still get a copy). expires_at is when the copy is deleted from our server (null when no cleanup window is active); a cached copy can stay reachable a little longer, so download it promptly and do not rely on the link disappearing.

Errors

StatusError
400"Provide exactly one of image_url, image_base64 or video_url"
400"image_url must be a public https URL" / "Invalid image_url: ..." / "Invalid image_base64" / "video_url must be a public https URL" / "Invalid video_url: ..."
400"The image URL could not be fetched." / "The video URL could not be fetched." (also when the download takes longer than 30 seconds)
403"Feature 'metadata_removal' is not enabled for this partner."
404"Image not found. ..." / "Video not found (or expired). ..." (a Knightvision URL that points at no file)
413"image_base64 too large (max 20 MB)" / "Image too large (max 20 MB)." / "Video too large (max 100 MB)."
415"Unsupported image format. Send a JPEG, PNG or WebP image." / "Unsupported video format. Send an MP4 or MOV video."
422"The image could not be processed. It may be damaged or not a valid JPEG, PNG or WebP file." / "The video could not be processed. It may be damaged or not a valid MP4 or MOV file."
429"Daily limit reached: 200 metadata removals per 24 hours. Try again later." (every attempt counts, failed ones included)
429"Daily limit reached: 50 video metadata removals per 24 hours. Try again later." (videos have their own quota)
503"Metadata removal is paused right now. Please try again later." / "Video metadata removal is busy right now. Please retry in a minute."

Example

curl -X POST https://knightvision.ai/api/v1/partner/remove-metadata \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://example.com/campaign/hero.jpg"}'
curl -X POST https://knightvision.ai/api/v1/partner/remove-metadata \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"video_url": "https://example.com/campaign/spot.mp4"}'

Social Downloader

Turn a public Instagram, TikTok or Facebook post into a browser-playable H.264 MP4 with audio, plus the platform's cover image. Free — these endpoints charge 0 credits; a daily quota per account is the limit instead. Files are stored for 7 days and are served only to your own API key.

Download Post

POST /api/v1/partner/social-download
Async Free — 0 credits

Queue one post for download. Answers immediately with a download_id; poll /social-download/<download_id> (usually ready in a few seconds) and then fetch the bytes from the files[].download_url it returns.

Request

{
  "url": "https://www.tiktok.com/@user/video/7300000000000000000",
  "partner_job_id": "lx-abc125"
}
FieldTypeRequiredDefaultDescription
urlstringyes—The post link (max 500 characters) on tiktok.com, instagram.com, facebook.com, fb.watch or fb.com — short links (vm., vt.) and mobile hosts included. A Facebook Ad Library link, or a bare numeric ad ID, also works. Any other host is a 400.
partner_job_idstringno—Optional cross-reference ID (e.g. lx-abc125), see Submit Ad Analysis for format.

Limits

LimitValue
Downloads per account200 per rolling 24 h (failed ones do not count)
In-flight downloads per account2
Duration600 seconds
Size200 MB per file and per job
Items per carousel10
Retention7 days, then the file route answers 410

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "download_id": "kv-7f3a9c21",
  "partner_job_id": "lx-abc125",
  "status": "pending",
  "platform": "tiktok",
  "normalized_url": "https://www.tiktok.com/@user/video/7300000000000000000",
  "retention_days": 7,
  "quota": {"limit": 200, "used": 12, "remaining": 188, "window_hours": 24, "resets_in_seconds": 51840}
}

Errors

StatusError
400"Only Facebook, Instagram and TikTok links are supported."
400"That link is too long." / "Only http(s) links are supported."
429"Daily limit reached — 200 downloads per 24 hours." (the body carries quota)
429"You already have 2 downloads running."
503"Downloads are paused right now. Please try again later."

Example

curl -X POST https://knightvision.ai/api/v1/partner/social-download \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.tiktok.com/@user/video/7300000000000000000"}'

Check Download Status

GET /api/v1/partner/social-download/<download_id>
Free

Poll until status is complete or failed. A download that is not yours answers 404.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "download_id": "kv-7f3a9c21",
  "partner_job_id": "lx-abc125",
  "status": "complete",
  "platform": "tiktok",
  "media_kind": "video",
  "title": "How we shot this",
  "uploader": "user",
  "duration_sec": 18.7,
  "width": 1080,
  "height": 1920,
  "has_audio": true,
  "bytes": 4271104,
  "file_count": 1,
  "retention_days": 7,
  "files": [
    {
      "idx": 0,
      "kind": "video",
      "download_name": "tiktok_7300000000000000000.mp4",
      "bytes": 4270976,
      "duration_sec": 18.7,
      "width": 1080,
      "height": 1920,
      "has_audio": true,
      "available": true,
      "stream_url": "/api/v1/partner/social-download/kv-7f3a9c21/file/0",
      "download_url": "/api/v1/partner/social-download/kv-7f3a9c21/file/0?download=1"
    },
    {
      "idx": 1,
      "kind": "cover",
      "for_idx": 0,
      "download_name": "tiktok_7300000000000000000_cover.jpg",
      "available": true,
      "stream_url": "/api/v1/partner/social-download/kv-7f3a9c21/file/1",
      "download_url": "/api/v1/partner/social-download/kv-7f3a9c21/file/1?download=1"
    }
  ],
  "error": null
}
A reel with no audio track is not an error — has_audio: false is the truth about that post. On failed, error is a plain sentence (“That post needs a login to open”, “That link has no downloadable video”); nothing is charged either way.

Fetch Downloaded File

GET /api/v1/partner/social-download/<download_id>/file/<idx>
Free

Stream one file of a finished download, authenticated with your API key (the bytes are never public). Supports Range requests; add ?download=1 for a Content-Disposition: attachment response. After the 7-day retention window this answers 410.

Example

curl -L https://knightvision.ai/api/v1/partner/social-download/kv-7f3a9c21/file/0?download=1 \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -o clip.mp4

List Downloads

GET /api/v1/partner/social-downloads
Free

Your recent downloads, newest first. Query: limit (default 25, max 100).

{
  "request_id": "a1b2c3d4e5f6",
  "downloads": [{"download_id": "kv-7f3a9c21", "status": "complete", "platform": "tiktok", "files": []}]
}

Video Generation

Generate AI videos via Kling V3 (text-to-video, image-to-video, Motion Control), Seedance 2.0 / 2.5 and Veo 3.1 (Quality / Fast). For consistent characters, first generate a reference image via Image Generation and pass its URL as image_url (start frame) to an image-to-video model — or, with seedance-2-direct / seedance-2-5, as one of the reference_image_urls. Both Seedance models also take reference videos (reference_video_urls, measured server-side and billed over reference + output seconds) and reference audio (reference_audio_urls: voice, music and rhythm, at no extra cost); Omni takes one reference video too. See the public AI Video Generator overview for the product workflow.

Generate Video

POST /api/v1/partner/generate-video
Async Variable credits

Generate an AI video. Poll /video-status/<job_id> for results.

Request

{
  "prompt": "A person walking through a futuristic city",
  "model": "kling-v3-text-to-video",
  "duration": 10,
  "aspect_ratio": "9:16",
  "quality": "1080p",
  "sound": true
}
FieldTypeRequiredDefaultDescription
promptstringyes—Video description, max 50000 chars (Kling models: 2500)
modelstringnokling-v3-text-to-videoSee models below
durationintegerno10Seconds. Validated per model — see the table below for accepted values. Sending an unsupported duration returns 400 with the list of valid values (no credits charged). Note for Veo: Veo always returns 8s regardless of the input duration. {4, 6, 8} is accepted for parity with the model's pacing knob, but the output length is fixed.
aspect_ratiostringno9:169:16, 16:9, or 1:1
qualitystringno720p720p, 1080p, or 4K (depends on model)
soundbooleannofalseAdd AI sound (Kling V3 only — included in the flat per-clip price, see the table below)
negative_promptstringno—Things to avoid in the video
image_urlstringno—HTTPS URL of the start frame. Supported by Kling V3 text-to-video / image-to-video, Seedance 2.0 / 2.5 and Veo 3.1 (Quality / Fast). Not supported by kling-v3-motion-control — see the table below. Kling text-to-video auto-switches to image-to-video when this field is provided.
image_base64stringno—Base64-encoded start frame, alternative to image_url. Max 20 MB binary (~28 M base64 chars). Pair with image_mime (defaults to image/jpeg).
end_image_urlstringno—HTTPS URL of the end frame for first+last-frame interpolation. Same model support as image_url. Requires image_url or image_base64 to also be set — first+last interpolation needs both frames.
end_image_base64stringno—Base64-encoded end frame, alternative to end_image_url. Same size + MIME rules as image_base64. Pair with end_image_mime.
reference_image_urlsarrayno—Seedance only (seedance-2-direct: up to 9, seedance-2-5: up to 30). Public HTTPS image URLs (a Knightvision image_url works) used as multimodal references — product shots, a character, a style board. Reference mode is separate from the start/end-frame mode: sending both returns 400. Reference images do not change the price.
reference_images_base64arrayno—Same as reference_image_urls, as an array of base64 strings (JPEG assumed; each ~20 MB binary max, ~72 MB combined). Both arrays can be mixed; the total is capped per model (9 / 30).
reference_video_urlsarrayno—Seedance only (seedance-2-direct: up to 3 clips, 2–15 s each and 15 s in total; seedance-2-5: up to 10 clips, 2–30 s each and 30 s in total). Public HTTPS URLs of MP4/MOV files, max 95 MB each — a Knightvision video URL from a previous job works. Each clip is downloaded, measured and normalized server-side before credits are charged; the measured length is part of the price (see the model table). Stacks with reference images; not combinable with a start/end frame. custom-tier accounts need a negotiated reference-video rate first (otherwise 400).
reference_audio_urlsarrayno—Seedance only (seedance-2-direct: up to 3 files, 2–15 s each and 15 s in total, only together with at least one reference image or video; seedance-2-5: up to 10 files, 2–30 s each and 30 s in total, audio on its own works). Public HTTPS URLs of MP3, WAV or M4A files, max 50 MB each. A Knightvision voiceover or music URL from a previous job works. Each file is downloaded, measured and converted to MP3 server-side before credits are charged. Guides voice, music and rhythm; name the files in your prompt in order as [Audio 1], [Audio 2]. Stacks with reference images and videos; not combinable with a start/end frame. Reference audio does not change the price.
partner_job_idstringno—Optional partner-supplied cross-reference ID (e.g. your own internal short hash like lx-abc123). 1-64 chars, alphanumeric + -/_. Persisted on the KV side and returned in subsequent GET responses so you can correlate KV jobs with your own admin dashboard. Not validated for uniqueness — you own your namespace.
callback_urlstringno—Public https URL that receives one signed event when this job completes or fails, see Webhooks. Overrides the account default for this job.
strip_metadatabooleannofalsetrue stores and serves the finished video without its AI-provenance metadata: the C2PA Content Credentials manifest plus container and stream tags. Lossless stream copy: frames and audio are untouched and invisible watermarks embedded in the frames stay. No extra cost. /video-status and /videos report the outcome as metadata_removed. Any value other than true or false is a 400, before any charge. To clean a video you already have, use Remove Metadata.

Models, Pricing & Constraints

ModelQualitiesCredits (api_plus10)Accepted durationsStart frameEnd frameReference images / videos / audio
kling-v3-text-to-video720p, 1080pFlat per clip — 720p: 55 (5s) / 110 (10s), 1080p: 72 / 165; +AI sound: 83 / 160 (720p), 110 / 220 (1080p)5, 10Yes (auto-switches to image-to-video)YesNo
kling-v3-image-to-video720p, 1080pFlat per clip — 720p: 55 (5s) / 110 (10s), 1080p: 72 / 165; +AI sound: 83 / 160 (720p), 110 / 220 (1080p)5, 10YesYesNo
kling-v3-motion-control720p, 1080p15.4/s (720p), 20.9/s (1080p)derived from motion videoNo — also requires a motion reference video (not yet exposed via partner API)NoNo
seedance-2-direct480p, 720p, 1080p, 4K15.4/s (480p), 31.9/s (720p), 79.2/s (1080p), 160.6/s (4K)permissive (no strict limit)Yes (first_frame_url via image_url / image_base64)Yes (first+last bookend mode)Images: up to 9 via reference_image_urls / reference_images_base64. Videos: up to 3 via reference_video_urls, 2–15 s each, 15 s in total — with a reference video the job bills 9.9/s (480p), 19.8/s (720p), 48.4/s (1080p), 99/s (4K) over reference + output seconds. Audio: up to 3 via reference_audio_urls, 2–15 s each, 15 s in total, only alongside a reference image or video, no extra charge. Not combinable with start/end frames
seedance-2-5480p, 720p, 1080p (no 4K)22/s (480p), 49.5/s (720p), 122.1/s (1080p)4–30 s (whole seconds)Yes (first_frame_url via image_url / image_base64)Yes (first+last bookend mode)Images: up to 30 via reference_image_urls / reference_images_base64. Videos: up to 10 via reference_video_urls, 2–30 s each, 30 s in total — with a reference video the job bills 13.2/s (480p), 29.7/s (720p), 73.7/s (1080p) over reference + output seconds. Audio: up to 10 via reference_audio_urls, 2–30 s each, 30 s in total, also on its own, no extra charge. Not combinable with start/end frames
veo-3.1-quality720p, 1080p, 4K165 / 196 / 220 (flat)4, 6, 8 — output is always 8sYesYesNo
veo-3.1-fast720p, 1080p, 4K46 / 54 / 132 (flat)4, 6, 8 — output is always 8sYesYesNo
Prices are api_plus10. Per-second rates are the standard rate × 1.1; the job total (rate × seconds) is rounded up to a whole credit once. Standard basis per second: Motion Control 14 / 19, Seedance 2.0 14 / 29 / 72 / 146, Seedance 2.5 20 / 45 / 111 — see GET /credits.

Duration is enforced. Sending a duration outside the accepted set for a model returns 400 with the list of valid values — no credits are charged. For Veo (fast + quality) the input is accepted as a pacing hint, but the output video is always 8 seconds long regardless of what you sent — plan your billing accordingly.

Kling V3 (text-to-video + image-to-video) is priced as a flat amount per clip for the supported 5s and 10s durations (see the table above). Setting sound: true selects the higher AI-sound per-clip rate — the audio cost is baked into that flat price, not a per-second add-on. Motion-control has no audio option. Veo models use flat pricing regardless of duration. Passing image_url, image_base64, end_image_url, or end_image_base64 to a model that doesn't accept reference frames returns 400 — no credits are charged. End-frame interpolation requires both image_* AND end_image_* — supplying only the end frame returns 400.

References vs. frames: reference_image_urls / reference_images_base64 / reference_video_urls / reference_audio_urls put either Seedance model into its multimodal reference mode (the model composes the scene from your references — images, videos and audio stack; refer to them in the prompt in order as [Image 1], [Video 1], [Audio 1]); image_url / end_image_url pin the first and last frame instead. The two modes cannot be combined in one request. Reference-video pricing: a job with at least one reference video is billed at the lower per-second rate shown in the table, but over the measured reference seconds plus the output seconds — a 5 s clip with a 5 s reference at 480p on seedance-2-5 costs 13.2 × 10 = 132 credits (rounded up once). That is how the provider bills video input; it is a rate swap, not a discount. Reference images never change the price. Neither does reference audio: it is converted to MP3 before credits are charged and guides the generated soundtrack (voice, music, rhythm) without being billed. The measured seconds come from the file we host, never from the request.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-7b2d9e10",
  "partner_job_id": null,
  "status": "queued",
  "credits_used": 220,
  "credits_remaining": 730
}

Errors

StatusError
400"Please provide a prompt" / "Prompt must be 50000 characters or less" (Kling models: 2500)
400"Invalid model. Allowed: ..." / "Invalid quality for <model>. Allowed: ..." / "Invalid aspect_ratio. Allowed: 9:16, 16:9, 1:1" / "Invalid duration"
400"image_url must be HTTPS" / "end_image_url must be HTTPS" / "Invalid image_url: ..."
400"Duration Ns is not supported for <model>. Allowed: ..."
400"strip_metadata must be true or false"
400"reference images are only supported for the Seedance models (seedance-2-5, seedance-2-direct)" / "reference images cannot be combined with a start/end frame — they are separate generation modes" / "Too many reference images (N). Max is 9 for seedance-2-direct." (30 for seedance-2-5)
400"reference videos are only supported for the Seedance models (seedance-2-5, seedance-2-direct)" / "reference videos cannot be combined with a start/end frame — they are separate generation modes" / "Too many reference videos (N). Max is 3 for seedance-2-direct." / "Reference videos must be between 2 and 15 seconds (yours is Xs)." / "Reference videos total Xs — the limit for seedance-2-direct is 15s." / "Invalid reference video URL: ..." / "A reference video could not be downloaded (HTTP N)." / "Could not prepare that video as a reference."
400"Reference videos are not available on your plan yet — contact us for a negotiated rate." — custom tier only
400"kling-v3-motion-control requires both a reference image AND a motion reference video, which the partner API does not currently support. Use kling-v3-image-to-video for image-only conditioning."
400"end_image_url/end_image_base64 requires a start frame (image_url or image_base64). First+last-frame interpolation needs both."
400"This resolution/duration is not available on your plan (<quality>, <duration>s)." — custom tier only, for a Seedance combination without a negotiated rate
413"image_base64 too large (max 20 MB)" / "end_image_base64 too large (max 20 MB)" / "a reference image is too large (max 20 MB)" / "reference images total size too large" / "A reference video is too large (max 95 MB)."
503"The reference encoder is busy right now — retry in a minute." — the reference-video normalizer is a single shared slot; retry after a short wait
402"Insufficient credits. This video costs N credits."
500"Video generation not available"

Example

curl -X POST https://knightvision.ai/api/v1/partner/generate-video \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "A person walking through a futuristic city", "model": "kling-v3-text-to-video", "duration": 10, "quality": "1080p"}'

Check Video Status

GET /api/v1/partner/video-status/<job_id>

Poll until status is complete or error. Any other value (queued, processing, ...) means the job is still running; those responses carry a progress percentage and a human-readable message. Animation jobs are polled here as well.

Response — Processing (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-7b2d9e10",
  "status": "processing",
  "progress": 40,
  "message": "Generating video..."
}

Response — Complete (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-7b2d9e10",
  "status": "complete",
  "video_url": "https://knightvision.tech/static/generated_videos/kv-7b2d9e10.mp4",
  "duration": 10,
  "metadata_removed": false
}

On "status": "error" the response carries a sanitized error string and the credits have already been refunded. metadata_removed is true when the request asked for strip_metadata and the stored file carries none of that metadata; otherwise it is false.

Example

curl https://knightvision.ai/api/v1/partner/video-status/kv-7b2d9e10 \
  -H "Authorization: Bearer kv_partner_your_key_here"

List Videos

GET /api/v1/partner/videos

List your generated videos, newest first.

Query Parameters

ParamTypeDefaultDescription
limitinteger20Max results (1-200)
offsetinteger0Skip first N results
statusstring—Only jobs in this state: processing, complete or failed
sincestring—Only jobs created at or after this UTC timestamp (ISO 8601, e.g. 2026-10-13T18:00:00Z). With status=processing one call lists everything still running since your order started.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "videos": [
    {
      "job_id": "kv-7b2d9e10",
      "partner_job_id": "lx-vid-001",
      "prompt": "A person walking through a futuristic city",
      "model": "kling-v3-text-to-video",
      "model_used": "kling-v3-text-to-video",
      "duration": 10,
      "aspect_ratio": "9:16",
      "credits_used": 110,
      "status": "complete",
      "video_url": "https://knightvision.tech/static/generated_videos/kv-7b2d9e10.mp4",
      "start_frame_url": null,
      "end_frame_url": null,
      "created_at": "2026-04-05 14:15:00",
      "metadata_removed": false
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0
}

Example

curl "https://knightvision.ai/api/v1/partner/videos?limit=10&offset=0" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Captions

Transcribe speech with on-device speech recognition and burn styled short-form captions into a new MP4. Captions is an independent asynchronous resource: start it from a completed video owned by your partner account, upload an MP4 directly, or use resumable chunked upload for large files. It is not a flag on /generate-video, and it never modifies the source video. See the public AI Captions overview for the product workflow.

Async 25 base credits

Poll GET /captions/<job_id> until the job reaches complete, no_speech, or failed. The states pending and processing are non-terminal. no_speech and first-run failures are automatically refunded. Correcting caption text and re-burning a completed job is free.

Media limits: MP4 only, maximum 30 minutes. Single-request multipart upload is limited to 95 MB; resumable upload supports up to 1,000,000,000 bytes (1 GB decimal). A high-resolution source can still be rejected when its estimated render time exceeds the worker safety budget. Remote video URLs are not accepted. The normal cleanup windows are 14 days for Caption output and 2 days for an uploaded edit source; both are non-guaranteed and may be shortened under storage pressure.
Output: an H.264 (8-bit 4:2:0) MP4 with the original audio stream. A source whose shorter side is under 1080 px is upscaled so its shorter side is 1080 px before the captions are drawn (a 720×1280 source returns 1080×1920), which keeps the text sharp. Captions stay on screen through short pauses, so a card is shown until the next one starts unless the speaker pauses longer than the chunking gap (0.6 s for words, 0.5 s for phrases, 0.35 s for line), and every card is shown for at least 0.3 s.

Get Style Options

GET /api/v1/partner/captions/style-options

Get the server-authoritative style whitelist, defaults, recommended slider ranges, and upload caps. Do not hard-code these values: new options may be added without a v1 breaking change. Unlike the signed-in app endpoint, the partner response has no per-user saved style. Custom position_y accepts 0–100, while rendering clamps it to the returned 5–95 safe area so text cannot leave the frame. presets are complete styles you can send as-is (the tiktok preset is the white grotesque caption with a stroked face and a soft shadow, no outline; minimal is the lowercase one-line caption in a Helvetica-style bold with tight letter spacing and a soft halo); chunking selects how words share a card: words (2–4 words, quick flips), phrases (up to 7 words, roughly 1.5–2.5 s per card) or line (never more than one line: a new card after every punctuation mark and after 4 words at the latest, measured against the frame in the chosen font and size; the screen clears during a pause). case sets the letter case of the burned text: normal (as transcribed), lower or upper; chunks you read or edit keep their spelling. Fonts with a non-zero embolden are burned with a stroke in the text colour of that fraction of the font size. fonts lists every caption font (62 in six categories: clean, bold, fun, serif, handwritten and retro); send a font's key as style.font and use category to group the list in your own picker.

Response (200 — abbreviated)

{
  "request_id": "a1b2c3d4e5f6",
  "fonts": [
    {"key": "inter", "label": "Inter", "file": "Inter-Bold.ttf", "css_family": "Inter", "embolden": 0, "category": "clean"},
    {"key": "montserrat", "label": "Montserrat", "file": "Montserrat-ExtraBold.ttf", "css_family": "Montserrat ExtraBold", "embolden": 0, "category": "clean"},
    {"key": "arimo", "label": "Arimo", "file": "Arimo-Regular.ttf", "css_family": "Arimo", "embolden": 0.036, "category": "clean"},
    {"key": "pacifico", "label": "Pacifico", "file": "Pacifico-Regular.ttf", "css_family": "Pacifico", "embolden": 0, "category": "handwritten"}
  ],
  "presets": [
    {"key": "tiktok", "label": "TikTok", "style": {"font": "arimo", "size": "medium", "color": "#FFFFFF",
      "outline_color": "#000000", "outline": "none", "shadow": "thin", "shadow_blur": 4, "shadow_x": 2,
      "shadow_y": 2.5, "shadow_strength": 70, "position": "bottom", "position_y": 50, "chunking": "phrases",
      "case": "normal"}}
  ],
  "chunking": ["words", "phrases", "line"],
  "cases": ["normal", "lower", "upper"],
  "sizes": ["small", "medium", "large"],
  "outlines": ["none", "thin", "medium", "thick"],
  "shadows": ["none", "thin", "medium", "thick"],
  "positions": ["bottom", "middle", "top", "custom"],
  "position_y": {"min": 5, "max": 95, "default": 50},
  "shadow_blur": {"min": 0, "max": 20, "default": 0, "step": 1},
  "shadow_x": {"min": -20, "max": 20, "default": null, "step": 1},
  "shadow_y": {"min": -20, "max": 20, "default": null, "step": 1},
  "shadow_strength": {"min": 0, "max": 100, "default": 100, "step": 1},
  "default": {
    "font": "inter", "size": "medium", "color": "#FFFFFF",
    "outline_color": "#000000", "outline": "medium", "shadow": "none",
    "shadow_blur": 0, "shadow_x": null, "shadow_y": null,
    "shadow_strength": 100, "position": "bottom", "position_y": 50,
    "chunking": "words", "case": "normal"
  },
  "credits": 25,
  "upload": {
    "chunked": true,
    "chunk_size": 8388608,
    "max_bytes": 1000000000,
    "max_seconds": 1800,
    "single_shot_max_bytes": 95000000
  },
  "retention": {
    "captioned_output_days": 14,
    "uploaded_source_days": 2,
    "guaranteed": false
  }
}

The abbreviated example omits size_values, color swatches, and the remaining font entries; use the live response as the source of truth. credits is the actual cost for your account's pricing tier (25 on standard, 28 on api_plus10).

Example

curl https://knightvision.ai/api/v1/partner/captions/style-options \
  -H "Authorization: Bearer kv_partner_your_key_here"

Caption a Generated Video

POST /api/v1/partner/captions
Async 25 base credits

Start a Caption job from a completed video owned by the same partner account. This is a separate post-processing job: first finish video generation, then submit its job_id here.

Request

{
  "video_job_id": "kv-1a2b3c4d",
  "partner_job_id": "campaign-42-captions",
  "style": {
    "font": "anton",
    "size": "large",
    "color": "#FFFFFF",
    "outline_color": "#000000",
    "outline": "thick",
    "shadow": "medium",
    "position": "bottom"
  }
}
FieldTypeRequiredDescription
video_job_idstringyesA completed Partner API video owned by your account. Arbitrary URLs and another account's jobs are rejected.
styleobjectnoAny subset of the fields returned in default by /captions/style-options. Missing fields use server defaults; unknown or out-of-range values return 400.
partner_job_idstringnoYour cross-reference ID (1-64 chars, alphanumeric plus - / _), returned by submit, status, and list responses.

Response (202)

The response contains the same lifecycle, availability, and retention fields as Check Caption Status, plus the post-charge credits_remaining balance. At submission the key fields are:

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-a1b2c3d4",
  "partner_job_id": "campaign-42-captions",
  "status": "pending",
  "credits_used": 25,
  "credits_remaining": 975
}

Example

curl -X POST https://knightvision.ai/api/v1/partner/captions \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"video_job_id":"kv-1a2b3c4d","partner_job_id":"campaign-42-captions","style":{"font":"anton","size":"large"}}'

Upload a Video

POST /api/v1/partner/captions/upload
Async 25 base credits

Upload one MP4 of at most 95,000,000 bytes as multipart/form-data. Use the chunked workflow below for larger files or resumable transfers. style is a JSON-encoded form field, not nested multipart keys.

Multipart Fields

FieldRequiredDescription
videoyesMP4 file, at most 95 MB.
stylenoJSON string containing a partial style object.
partner_job_idnoYour optional cross-reference ID.

Response (202)

The response has the same fields as Caption a Generated Video; its retention object reports uploaded_source_days: 2.

Example

curl -X POST https://knightvision.ai/api/v1/partner/captions/upload \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -F "video=@./source.mp4;type=video/mp4" \
  -F 'style={"font":"montserrat","position":"bottom"}' \
  -F "partner_job_id=campaign-42-upload"

Chunked Upload

For files up to 1 GB, create an upload session, send every server-sized part, then finalize. Init, parts, and abort never deduct credits. Finalize validates and assembles the MP4, then performs the single credit deduction and starts the Caption job.

1. Initialize

POST /api/v1/partner/captions/uploads/init
{
  "filename": "source.mp4",
  "size_bytes": 240000000,
  "partner_job_id": "campaign-42-large",
  "style": {"font": "anton", "size": "large"}
}

style and partner_job_id are optional. The session persists both. Finalize may override style, but partner_job_id is fixed at initialization.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "session_id": "kv-e5f6a7b8",
  "partner_job_id": "campaign-42-large",
  "chunk_size": 8388608,
  "part_count": 29,
  "max_bytes": 1000000000,
  "max_seconds": 1800,
  "credits": 25,
  "retention": {
    "captioned_output_days": 14,
    "uploaded_source_days": 2,
    "guaranteed": false
  }
}

Example

curl -X POST https://knightvision.ai/api/v1/partner/captions/uploads/init \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"filename":"source.mp4","size_bytes":240000000,"partner_job_id":"campaign-42-large"}'

2. Upload Every Part

POST /api/v1/partner/captions/uploads/<session_id>/part/<index>

Send a raw application/octet-stream body. Part indexes are zero-based. Every part except the last must be exactly chunk_size bytes; the last part must contain the exact remainder. Re-sending the same part is safe and replaces that index atomically.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "index": 0,
  "received_parts": 1,
  "received_bytes": 8388608,
  "total_bytes": 240000000
}
curl -X POST https://knightvision.ai/api/v1/partner/captions/uploads/kv-e5f6a7b8/part/0 \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./part-0000.bin

3. Finalize

POST /api/v1/partner/captions/uploads/<session_id>/finalize
{
  "style": {"font": "anton", "size": "large", "position": "bottom"}
}

Returns the normal Caption submit response (202). A repeat after a successful finalize returns the same job (200) and does not deduct again. If parts are missing, the 409 response contains a missing array; upload those indexes and finalize again.

curl -X POST https://knightvision.ai/api/v1/partner/captions/uploads/kv-e5f6a7b8/finalize \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"style":{"font":"anton","size":"large"}}'

Abort

POST /api/v1/partner/captions/uploads/<session_id>/abort

Abort an open session and remove its stored parts. Abandoned sessions are also garbage-collected. Once finalization starts, continue polling the resulting Caption job instead of treating it as cancelled.

{"request_id": "a1b2c3d4e5f6", "ok": true, "session_id": "kv-e5f6a7b8"}
curl -X POST https://knightvision.ai/api/v1/partner/captions/uploads/kv-e5f6a7b8/abort \
  -H "Authorization: Bearer kv_partner_your_key_here"

Check Caption Status

GET /api/v1/partner/captions/<job_id>

Poll one Caption job owned by your partner account. A terminal response always uses HTTP 200; inspect status. captioned_url is an absolute URL only while retained.

Response (200 — complete)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-a1b2c3d4",
  "partner_job_id": "campaign-42-captions",
  "status": "complete",
  "captioned_url": "https://knightvision.tech/static/captioned_videos/kv-a1b2c3d4.mp4",
  "media_pruned": false,
  "credits_used": 25,
  "credits_refunded": false,
  "edit_count": 0,
  "editable": true,
  "source_available": true,
  "retention": {
    "captioned_output_days": 14,
    "uploaded_source_days": null,
    "guaranteed": false
  },
  "error": null,
  "created_at": "2026-08-15 12:00:00",
  "completed_at": "2026-08-15 12:00:14"
}
StatusMeaning
pendingQueued; keep polling.
processingTranscribing or rendering; keep polling.
completeOutput is ready. Download captioned_url before retention expires.
no_speechNo usable speech was found. Terminal; credits were refunded.
failedTerminal first-run failure; credits were refunded and error is sanitized.

editable is true only for a completed job whose source is still available and whose transcript fits the inline editing limit; source_available exposes the source check separately. Output retention is independent, so a still-downloadable output may no longer be editable, while a pruned output can still be re-burned when its source remains. uploaded_source_days is 2 for direct uploads and null for a Partner API generated-video source, which follows that product's own retention. Retention is a cleanup target, not guaranteed storage; retention.guaranteed is false. Always inspect the live URL/availability fields and download output promptly.

Example

curl https://knightvision.ai/api/v1/partner/captions/kv-a1b2c3d4 \
  -H "Authorization: Bearer kv_partner_your_key_here"

List Captions

GET /api/v1/partner/captions

List your Caption jobs newest first. Supports ?limit= (default 20, max 50) and ?offset= (default 0).

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "captions": [
    {
      "job_id": "kv-a1b2c3d4",
      "partner_job_id": "campaign-42-captions",
      "status": "complete",
      "captioned_url": "https://knightvision.tech/static/captioned_videos/kv-a1b2c3d4.mp4",
      "media_pruned": false,
      "credits_used": 25,
      "credits_refunded": false,
      "edit_count": 0,
      "editable": true,
      "source_available": true,
      "retention": {
        "captioned_output_days": 14,
        "uploaded_source_days": null,
        "guaranteed": false
      },
      "error": null,
      "created_at": "2026-08-15 12:00:00",
      "completed_at": "2026-08-15 12:00:14"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}

Example

curl "https://knightvision.ai/api/v1/partner/captions?limit=20&offset=0" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Get Editable Chunks

GET /api/v1/partner/captions/<job_id>/chunks

Return the current caption text and timings. Prior edits win over the automatic transcript. Very long transcripts may return no inline chunks with too_many_to_edit: true.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-a1b2c3d4",
  "partner_job_id": "campaign-42-captions",
  "status": "complete",
  "chunks": [
    {"start": 0.42, "end": 1.31, "text": "Create better ads"},
    {"start": 1.32, "end": 2.15, "text": "in less time"}
  ],
  "chunk_count": 2,
  "too_many_to_edit": false,
  "max_inline_edit_chunks": 800,
  "editable": true,
  "source_available": true
}

Example

curl https://knightvision.ai/api/v1/partner/captions/kv-a1b2c3d4/chunks \
  -H "Authorization: Bearer kv_partner_your_key_here"

Edit & Re-burn

POST /api/v1/partner/captions/<job_id>/edit
Async Free re-burn

Replace the complete chunk list of a completed Caption job, then asynchronously re-burn it with the job's existing style. Keep each chunk's timing and change text; an empty text removes that caption. Edited chunks are burned with the same display timing as automatic captions (held through short pauses, at least 0.3 s each). This does not transcribe again and deducts no credits.

Request

{
  "chunks": [
    {"start": 0.42, "end": 1.31, "text": "Create stronger ads"},
    {"start": 1.32, "end": 2.15, "text": "in less time"}
  ]
}

Send at most 4000 chunks. Each non-empty text is limited to 200 characters; start/end must be numeric with 0 ≤ start < end ≤ 7200. Empty text removes that chunk, but at least one non-empty caption must remain.

Response (202)

The response has the same lifecycle, media, credit, and retention fields as Check Caption Status, with status: "pending". No new charge is made.

Poll the normal status endpoint again. A successful edit increments edit_count and returns a new output URL. If re-burning fails, the previous completed output is restored and no refund is issued because the original paid result remains available.

Example

curl -X POST https://knightvision.ai/api/v1/partner/captions/kv-a1b2c3d4/edit \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"chunks":[{"start":0.42,"end":1.31,"text":"Create stronger ads"}]}'

Caption Errors

StatusWhen
400Invalid video_job_id, style, MP4, duration, part size/index, or edited chunks.
402Insufficient partner credits. No Caption job is started.
403The captions feature is disabled for this partner account.
404Video, Caption job, or upload session does not exist or belongs to another partner.
409Source video is not complete; the same source already has an active Caption job; upload parts are missing; finalization/editing is already in progress; or the job is not currently editable.
410The source media required to start or re-burn the job has been pruned.
413Single upload exceeds 95 MB or chunked upload exceeds 1 GB.
429Endpoint rate limit or partner in-flight job limit exceeded.
503Upload storage is temporarily unavailable, or the Caption job/re-burn could not be queued safely.

Worker and media-tool details are never exposed. Terminal status responses use fixed, sanitized error text and report whether the original charge was refunded.

Animations

Generate a single animated clip that interpolates between two key frames (a start frame and an end frame) with AI motion and sound. Settings are locked to the production Animations preset: 8 second duration, 1080p, audio on. Provide a pair of frames per clip; to animate a sequence of N frames, submit N−1 clips (frame 1→2, 2→3, …) and stitch them with Merge Videos.

Generate Animation

POST /api/v1/partner/generate-animation
172 credits Async

Create one animated clip from a start + end frame. Poll Check Video Status with the returned job_id; animations are stored alongside your other videos, so List Videos returns them too.

Request

FieldTypeRequiredDescription
promptstringYesDescribes the motion / transition between the two frames. Max 2500 characters.
start_image_urlstringYes*Public HTTPS URL of the first frame.
start_image_base64stringYes*Base64 of the first frame (max ~20 MB binary). Alternative to start_image_url. Pair with start_image_mime (default image/jpeg).
end_image_urlstringYes*Public HTTPS URL of the last frame.
end_image_base64stringYes*Base64 of the last frame (max ~20 MB binary). Alternative to end_image_url. Pair with end_image_mime (default image/jpeg).
aspect_ratiostringNo9:16 (default), 16:9, or 1:1.
partner_job_idstringNoYour cross-reference ID (1-64 chars, alphanumeric plus - / _).
callback_urlstringNoPublic https URL that receives one signed event when this job completes or fails, see Webhooks. Overrides the account default for this job.

* Both a start frame and an end frame are required — supply each as either a URL or base64. Duration (8s), resolution (1080p) and audio (on) are fixed for this endpoint. Price: 172 credits on api_plus10 (standard basis 156).

Response (202 Accepted)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-5d4c3b2a",
  "partner_job_id": "lx-anim-1",
  "status": "queued",
  "credits_used": 172,
  "credits_remaining": 828
}

Example

curl -X POST https://knightvision.ai/api/v1/partner/generate-animation \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "smooth morph between the two product shots",
    "start_image_url": "https://example.com/frame1.jpg",
    "end_image_url": "https://example.com/frame2.jpg",
    "aspect_ratio": "9:16"
  }'

Merge Videos

POST /api/v1/partner/merge-videos
110 credits

Concatenate 2 or more of your own completed clips into a single MP4, in the order given — handy for stitching the per-pair clips of a multi-frame animation. Synchronous: the response contains the final merged_url. Price: 110 credits on api_plus10 (standard basis 100). Merged clips are kept for about 7 days — download the file promptly; the source clips are unaffected.

Request

FieldTypeRequiredDescription
job_idsarrayYes2 or more job_ids of your completed clips, in merge order.
partner_job_idstringNoYour cross-reference ID.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "merge_id": "kv-6e5f4a3b",
  "partner_job_id": null,
  "merged_url": "https://knightvision.tech/static/merged_videos/kv-6e5f4a3b.mp4",
  "credits_used": 110,
  "credits_remaining": 718
}

Errors

StatusError
400"Need at least 2 job_ids" / "Invalid job_id: ..."
404"Some clips not found, not owned, or not complete" / "Video file missing for ..."
402"Insufficient credits. Merging costs N credits."
502"Merge failed" — credits refunded

Example

curl -X POST https://knightvision.ai/api/v1/partner/merge-videos \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"job_ids": ["kv-5d4c3b2a", "kv-7b2d9e10"]}'

Omni

Create reusable characters from a reference image, then generate character-consistent videos with Gemini Omni. Build a character once and reference it (0–3 per clip) across many videos for a consistent face/identity. Voices (free) give the speech a consistent sound: build one on a preset voice, then bake it into a character or add up to 3 to a video. Characters and voices are created synchronously; videos are async (poll for the result).

Create Character

POST /api/v1/partner/omni/characters
33 credits

Create one Omni character. Synchronous — the response includes the character id. Provide the reference image as image_base64 (preferred) or a public image_url. Price: 33 credits on api_plus10 (standard basis 30).

Request

FieldTypeRequiredDescription
namestringYesDisplay name (max 120 chars). Also used as the identity description when description is empty.
descriptionstringNoIdentity / style description (max 2000 chars).
image_base64stringYes*Base64 reference image (max ~20 MB binary). PNG/JPEG/WebP.
image_urlstringYes*Public HTTPS reference image. Use instead of image_base64.
voice_idstringNoOne of your voices. It is fixed when the character is created, so the character speaks with it in every clip.
partner_job_idstringNoYour cross-reference ID.

* Provide exactly one of image_base64 or image_url.

Response (201)

{
  "request_id": "a1b2c3d4e5f6",
  "character_id": "kv-2a3b4c5d",
  "partner_job_id": null,
  "name": "Spokesperson",
  "description": "friendly presenter in a navy blazer",
  "image_url": "https://knightvision.tech/static/omni_characters/kv-2a3b4c5d.jpg",
  "voice_id": null,
  "credits_used": 33,
  "credits_remaining": 967
}

Errors

StatusError
400"name is required" / "Provide image_base64 or image_url" / "Invalid image_base64" / "Invalid image_url: ..." / "Selected voices not found in your library."
413"image_base64 too large (max 20 MB)"
402"Insufficient credits. A character costs N credits."
502Hosting the image or creating the character failed — credits refunded
503"Omni is currently unavailable"

List Characters

GET /api/v1/partner/omni/characters

List your Omni characters, newest first. Returns the whole library (no pagination).

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "characters": [
    {
      "character_id": "kv-2a3b4c5d",
      "partner_job_id": null,
      "name": "Spokesperson",
      "description": "friendly presenter in a navy blazer",
      "image_url": "https://knightvision.tech/static/omni_characters/kv-2a3b4c5d.jpg",
      "voice": {"id": "kv-3c4d5e6f", "name": "Warm narrator", "base_voice": "sulafat", "base_voice_name": "Sulafat"},
      "created_at": "2026-06-26 10:00:00"
    }
  ]
}

voice is the voice baked into the character, or null. It stays set after the voice is deleted from your library, because the character keeps sounding that way.

Delete Character

DELETE /api/v1/partner/omni/characters/<character_id>

Delete one of your characters. Returns { "ok": true, "deleted_id": "kv-2a3b4c5d" }; an unknown or foreign id is 404 "Not found".

Voice Presets

GET /api/v1/partner/omni/voice-presets

The 30 preset voices a new voice is built on. Pass an id as base_voice to Create Voice. sample_url is a short preview of the preset.

{
  "request_id": "a1b2c3d4e5f6",
  "presets": [
    {"id": "kore", "name": "Kore", "gender": "female", "traits": "Firm, mid pitch", "sample_url": "https://knightvision.tech/static/omni_voice_samples/kore.mp3"},
    {"id": "puck", "name": "Puck", "gender": "male", "traits": "Upbeat, mid pitch", "sample_url": "https://knightvision.tech/static/omni_voice_samples/puck.mp3"}
  ]
}
idGenderTraits
achernarfemaleSoft, high pitch
aoedefemaleBreezy, mid pitch
autonoefemaleBright, mid pitch
callirrhoefemaleEasy-going, mid pitch
despinafemaleSmooth, mid pitch
erinomefemaleClear, mid pitch
gacruxfemaleMature, mid pitch
korefemaleFirm, mid pitch
laomedeiafemaleUpbeat, mid-high pitch
ledafemaleYouthful, mid-high pitch
sulafatfemaleWarm, mid pitch
vindemiatrixfemaleGentle, mid pitch
zephyrfemaleBright, mid-high pitch
achirdmaleFriendly, mid pitch
algenibmaleGravelly, low pitch
algiebamaleSmooth, mid-low pitch
alnilammaleFirm, mid-low pitch
charonmaleInformative, lower pitch
enceladusmaleBreathy, lower pitch
fenrirmaleExcitable, younger pitch
iapetusmaleClear, mid-low pitch
orusmaleFirm, mid-low pitch
puckmaleUpbeat, mid pitch
rasalgethimaleInformative, mid pitch
sadachbiamaleLively, low pitch
sadaltagermaleKnowledgeable, mid pitch
schedarmaleEven, mid-low pitch
umbrielmaleEasy-going, lower pitch
zubenelgenubimaleCasual, mid-low pitch
pulcherrimaneutralForward, mid-high pitch

Create Voice

POST /api/v1/partner/omni/voices
Free

Create a reusable voice. Synchronous and free. Attach it to a character at creation (voice_id) or add up to 3 voices to a video (voice_ids). A video costs the same with or without voices.

Request

FieldTypeRequiredDescription
base_voicestringYesA preset id from Voice Presets, e.g. kore.
namestringYesDisplay name (max 120 chars).
descriptionstringNoTimbre, style, pace or emotion (max 2000 chars).
example_dialoguestringNoA sample line in this voice (max 120 chars).
partner_job_idstringNoYour cross-reference ID.

Response (201)

{
  "request_id": "a1b2c3d4e5f6",
  "voice_id": "kv-3c4d5e6f",
  "partner_job_id": null,
  "name": "Warm narrator",
  "base_voice": "sulafat",
  "base_voice_name": "Sulafat",
  "gender": "female",
  "traits": "Warm, mid pitch",
  "description": "calm, friendly, unhurried",
  "example_dialogue": "Let me show you how it works.",
  "sample_url": "https://knightvision.tech/static/omni_voice_samples/sulafat.mp3",
  "created_at": "2026-09-25 10:00:00"
}

Errors

StatusError
400"Unknown base voice. Pick one of the preset voices." / "Name is required."
502"Voice could not be created. Please try again."
503"Omni is currently unavailable."

List Voices

GET /api/v1/partner/omni/voices

List your voices, newest first. Returns { "request_id": "...", "voices": [ ... ] } with the same fields as the Create Voice response.

Delete Voice

DELETE /api/v1/partner/omni/voices/<voice_id>

Delete one of your voices. Returns { "ok": true, "deleted_id": "kv-3c4d5e6f" }; an unknown or foreign id is 404 "Not found". Characters created with the voice keep it.

Generate Omni Video

POST /api/v1/partner/omni/video
132–231 credits Async

Generate a Gemini Omni video, optionally referencing your characters (up to 3), reference images and one reference video — all counted against one budget of 7 (see below). Poll Check Status with the returned job_id.

Models: omni (Omni Flash, default) and omni-1.1 (Omni 1.1 Flash). Both cost the same and take the same references, characters and voices; omni-1.1 can additionally start on an image of your choice and end on another (first_frame_url / last_frame_url).

Request

FieldTypeRequiredDescription
promptstringYesScene description (max 10000 chars).
modelstringNoomni (default) or omni-1.1. Same price. Any other value is a 400 before any charge.
first_frame_urlstringNoomni-1.1 only. Public HTTPS URL of the image the clip starts on. A start frame cannot be combined with character_ids, voice_ids, image_urls / image_base64 or a reference video (400 before any charge). No extra cost.
last_frame_urlstringNoomni-1.1 only, together with first_frame_url. Public HTTPS URL of the image the clip ends on.
character_idsarrayNo0–3 of your character ids for identity consistency.
voice_idsarrayNo0–3 of your voice ids for the speech in the clip. Write the spoken lines into prompt. Voices are outside the reference budget and do not change the price.
image_urlsarrayNoPublic HTTPS URLs of reference images guiding scene, style or storyboard. Max 20 MB each.
image_base64arrayNoReference images as base64 (PNG, JPEG or WEBP), instead of or alongside image_urls. Max ~20 MB binary each, ~72 MB combined.
video_urlstringNoPublic HTTPS URL of ONE reference video guiding the clip. Source must be ≤30 s and ≤90 MB. With a reference video the model determines the output length; your duration still selects the price tier. Adds the surcharge below.
video_start / video_endnumberWith video_urlTrim window in seconds — the part of the source actually used. video_end − video_start must be ≤10 s.
durationintegerNo4, 6, 8 (default), or 10 seconds.
aspect_ratiostringNo9:16 (default) or 16:9.
resolutionstringNo1080p (default) or 4k.
seedintegerNoOptional deterministic seed.
partner_job_idstringNoYour cross-reference ID.
callback_urlstringNoPublic https URL that receives one signed event when this job completes or fails, see Webhooks. Overrides the account default for this job.
strip_metadatabooleanNotrue stores and serves the finished clip without its AI-provenance metadata (C2PA Content Credentials manifest plus container and stream tags); lossless stream copy, invisible watermarks stay, no extra cost. Check Status and List Omni Videos report the outcome as metadata_removed. Any value other than true or false is a 400 ("strip_metadata must be true or false"), before any charge.

Reference budget: all references share one limit — image_urls + image_base64 + 2× reference video + character_ids must not exceed 7 combined (a video counts double). Exceeding it returns 400 before any credits are charged. Reference images do not change the price; a reference video adds the surcharge below. voice_ids are not part of this budget.

Per-clip pricing (credits, api_plus10)

Resolution4s6s8s10s
1080p132143154165
4k198209220231
Reference-video surcharge (either resolution)+79+63+47+32

Standard basis: 120 / 130 / 140 / 150 (1080p) and 180 / 190 / 200 / 210 (4k) — see costs.omni.video_price in GET /credits. The reference-video surcharge is a flat add-on: it is added after the tier markup and is the same on every tier. Partners whose contract includes negotiated reference-video rates pay those all-in rates instead.

Response (202 Accepted)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-9f8e7d6c",
  "partner_job_id": null,
  "status": "processing",
  "model": "omni",
  "duration": 8,
  "aspect_ratio": "9:16",
  "resolution": "1080p",
  "credits_used": 154,
  "credits_remaining": 846
}

Example

curl -X POST https://knightvision.ai/api/v1/partner/omni/video \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "the spokesperson presents the product on a bright set",
    "character_ids": ["kv-2a3b4c5d"],
    "voice_ids": ["kv-3c4d5e6f"],
    "duration": 8,
    "resolution": "1080p"
  }'

Check Omni Video Status

GET /api/v1/partner/omni/video/<job_id>

Poll until status is complete or error (processing while the clip renders). Credits are auto-refunded on failure. When a failed job's error is a prompt-fixable safety block, prompt_fixable is true and Fix Prompt can rewrite and resubmit it.

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-9f8e7d6c",
  "status": "complete",
  "model": "omni",
  "prompt": "the spokesperson presents the product on a bright set",
  "character_ids": ["kv-2a3b4c5d"],
  "voice_ids": ["kv-3c4d5e6f"],
  "first_frame_url": null,
  "last_frame_url": null,
  "duration": 8,
  "aspect_ratio": "9:16",
  "resolution": "1080p",
  "video_url": "https://knightvision.tech/static/omni_videos/kv-9f8e7d6c/video.mp4",
  "error": null,
  "prompt_fixable": false,
  "credits_used": 154,
  "credits_refunded": 0,
  "created_at": "2026-06-26 10:05:00",
  "metadata_removed": false
}

List Omni Videos

GET /api/v1/partner/omni/videos

List your Omni videos, newest first. Supports ?limit= (1–200), ?offset=, ?status= (processing, complete or failed) and ?since= (ISO 8601 UTC, jobs created at or after it). Each entry carries model and metadata_removed like Check Status.

Omni Script Runner

POST /api/v1/partner/omni/script-runner
per section, by duration Async

Generate one Omni video clip per section, all using the same character, in a single batch call. Locked to 1080p; each section may pick its own duration (default 10s). Returns a batch_id and one job_id per section — poll each via Check Status, or the whole batch via the batch-status endpoint below.

Request

FieldTypeRequiredDescription
character_idstringYesOne of your character ids — the default for every section.
sectionsarrayYes1–30 sections, each { "id"?, "title"?, "content", "character_id"?, "duration"?, "image_base64"? }. content max 10000 chars.
sections[].character_idstringNoOptional per-section override — one of your character ids; this section's clip runs with that character instead of the run-level one.
sections[].durationintegerNoClip length in seconds for this section: 4, 6, 8 or 10 (default 10). Billing is per section by duration; the response reports each section's duration and credits_used.
sections[].image_base64stringNoOptional per-section reference image used for that section's clip. PNG, JPEG or WEBP as base64 (a data: URI prefix is tolerated). Max ~20 MB binary per image, ~72 MB combined across all sections.
aspect_ratiostringNo9:16 (default) or 16:9.
partner_job_idstringNoYour cross-reference ID.

Response (202 Accepted)

{
  "request_id": "a1b2c3d4e5f6",
  "batch_id": "kv-4b3a2c1d",
  "partner_job_id": null,
  "status": "processing",
  "aspect_ratio": "9:16",
  "resolution": "1080p",
  "section_count": 2,
  "jobs": [
    {"section_id": "hook", "title": "HOOK", "job_id": "kv-1c2d3e4f", "duration": 8, "credits_used": 154},
    {"section_id": "body", "title": "BODY", "job_id": "kv-5a6b7c8d", "duration": 10, "credits_used": 154}
  ],
  "credits_used": 308,
  "credits_remaining": 692
}

Per-section price on api_plus10: 132 / 143 / 154 / 154 for 4 / 6 / 8 / 10 s (standard basis 120 / 130 / 140 / 140 — a 10 s section is capped at the 8 s price).

Batch status & list

GET /api/v1/partner/omni/script-runner/<batch_id>

Aggregate status for one batch: per-section status/video_url, plus sections_complete and an overall status of processing, complete, partial, or error.

GET /api/v1/partner/omni/script-runner

List your Script Runner batches, newest first. Supports ?limit= (1–100) and ?offset=.

Fix Prompt

POST /api/v1/partner/omni/improve-prompt
2 credits

Rewrite the stored prompt of one of your failed Omni video jobs to satisfy content-safety guidelines, then re-submit with the improved prompt. Only works for failed jobs whose error is a prompt-fixable safety block (not provider/internal errors) — prompt_fixable in the status response tells you. Synchronous; refunds on failure. Price: 2 credits on api_plus10 (standard basis 1).

Request

FieldTypeRequiredDescription
job_idstringYesThe failed Omni video job_id to fix.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-0d1e2f3a",
  "source_job_id": "kv-9f8e7d6c",
  "improved_prompt": "a person dancing on a bright set",
  "credits_used": 2,
  "credits_remaining": 998
}

Errors

StatusError
400"job_id is required" / "This job did not fail, so there is nothing to fix." / "This error can't be fixed by editing the prompt."
404"Not found"
402"Insufficient credits. This costs N credits."
502 / 503The prompt improver is temporarily unavailable — credits refunded

Script Runner

Turn a structured prompt (scene + dialogue blocks) and a single character image into a full set of UGC-style videos in one call. Each scene becomes a 9:16 frame (Nano Banana Pro or GPT Image 2, selectable via image_model) followed by a video clip in the model of your choice (Veo 3.1 Fast / Quality, Kling V3, Seedance 2.0 or Seedance 2.5).

Start Script Runner Run

POST /api/v1/partner/script-runner
55+ credits per scene (standard basis) Async

Turn a structured prompt (scene / dialogue blocks) plus a character image into a set of UGC-style videos. Each scene produces one 9:16 frame (image gen — 14 credits with the default nano-banana-pro, 24 credits with gpt-image-2) followed by one video clip in the selected model. Per-scene cost depends on both models — see Pricing per model below. Credits are deducted upfront; any failed scene is automatically refunded.

Request

{
  "name": "My Run",
  "character_image_url": "https://example.com/character.jpg",
  "use_as_character_reference": true,
  "prompt": "=== SCENE: Living Room ===\nModern apartment, afternoon light, sofa.\n\n=== SCENE: Kitchen ===\nBright kitchen, character holding a coffee mug.\n\n=== DIALOGUE 1 ===\nI used to dread mornings...\n\n=== DIALOGUE 2 ===\nMy friends keep asking me what I changed.\n"
}
FieldTypeRequiredDescription
character_image_urlstringconditionalPublic http(s) image URL. Private / loopback / link-local addresses are rejected. Provide this, character_image_base64, or a === CHARACTER === ... block in the prompt.
character_image_base64stringconditionalBase64-encoded image (PNG, JPEG, or WEBP), max ~20 MB binary (≈28 M base64 characters). Alternative to character_image_url. If both are provided, the URL wins. A data:image/...;base64, prefix is stripped automatically.
promptstringyes=== SCENE: Name === / === DIALOGUE N === block format. Max 20 scenes. May also include a === CHARACTER === ... block; see Character source below. Reserved: a first line of the form CHARACTER: <name or id>[, ...] inside a scene body is metadata, not scene description — see Reserved scene line below.
namestringnoLabel for the run (max 200 chars). Default "Script Runner Run".
use_as_character_referencebooleannoSelects the run mode (see Modes below). Default true.
modelstringnoVideo model used for every scene in this run. Default veo-3.1-fast. See Pricing per model below for allowed values and per-scene cost.
image_modelstringnoImage model for the scene frames (and character generation, if used). nano-banana-pro (default, 14 credits/image) or gpt-image-2 (24 credits/image — adds 10 credits per scene to the totals below).
partner_job_idstringnoOptional cross-reference ID (e.g. lx-abc123), see Submit Ad Analysis for format.

Pricing per model

Each scene costs frame + video credits on the standard basis. The frame cost depends on image_model: 14 with nano-banana-pro (default, totals below) or 24 with gpt-image-2 (add 10 to each total). The video cost is fixed per model at the default duration / quality used by Script Runner (720p, no sound). The total per scene is what the API returns as cost_per_scene.

ModelDefault durationVideo cost (standard basis)cost_per_scene (standard basis)
veo-3.1-fast (default)8 s4155
veo-3.1-quality8 s150164
kling-v3-text-to-video10 s100114
seedance-2-direct5 s @ 720p145159
seedance-2-55 s @ 720p225239
What a run bills: cost_per_scene × scene_count (plus one extra frame when the character is generated from a description), then your tier is applied to the run total and rounded up once. On api_plus10 a 2-scene run with the default model bills 121 credits (55 × 2 × 1.1, rounded up); cost_per_scene in the response stays the standard-basis 55. A failed scene refunds its standard-basis share.

Character source

You must supply the character in exactly one of three ways. The image-based options skip generation; the description-based option costs one extra image (14 credits with nano-banana-pro, 24 with gpt-image-2) before the scenes start rendering.

SourceHow to sendExtra costWhen to use
Image URLcharacter_image_url = public http(s) URL0Image is already hosted somewhere reachable (CDN, S3, your own server).
Image uploadcharacter_image_base64 = base64-encoded image bytes0Your UI lets users drag-and-drop a file; you forward the bytes inline.
DescriptionInclude a === CHARACTER === ... block in prompt (no image fields)+14 credits (nano-banana-pro) / +24 (gpt-image-2)You don't have a character image yet — let the pipeline synthesize one from the description first.

If both an image and a === CHARACTER === ... block are provided, the image wins and the description is ignored.

Reserved scene line

Since 2026-08-13 the very first line of a scene body may be CHARACTER: <name>. It is read as metadata and removed from the scene description before the frame is generated — it never reaches the image prompt. The name selects a per-scene character in the in-app Script Runner; this endpoint has a single run-level character source (above) and therefore ignores the name, but still strips the line so both readers see the same script.

Since 2026-08-14 the line may name several characters, separated by commas (CHARACTER: Anna, Max) — the in-app Script Runner casts up to 3 per scene. Names are trimmed, empty entries are dropped and duplicates collapse case-insensitively. This endpoint still ignores them and only strips the line.

A token may also be a character ID (kv- + 8 hex, e.g. CHARACTER: kv-1a2b3c4d), mixable with names on the same line. An ID resolves to exactly one character, so it is the only way to address a name that is not unique in the library, or one that itself contains the comma separator.

Two ways to keep such a line as ordinary scene text: put it anywhere other than the first line, or write a space before the colon (CHARACTER : ...). Matching is case-insensitive and requires no space before the colon.

=== SCENE: Kitchen ===
CHARACTER: Anna          <-- metadata, stripped from the prompt
Bright kitchen, morning light.

Modes

The use_as_character_reference flag picks one of two run modes. Both modes work with every supported video model and require a character source (see above).

ModeFlagBehaviourBest for
Character-Locked (default) true Frame generation uses the character image as a reference, and the same image is also passed to the video model as an additional character reference. Strongest face / outfit consistency across the clip. (When the character is generated from a description, the synthesized image is what gets passed.) UGC-style ads where a recognizable creator must stay on-model in every scene.
Scene-Driven false Frame generation still uses the character image as the anchor for the start frame, but the video model receives only that start frame — no separate character reference. More creative freedom in motion / camera, with a higher chance of facial drift over the clip. Action-heavy or environment-driven scenes where physical movement matters more than perfect face consistency.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-8e7d6c5b",
  "partner_job_id": null,
  "status": "processing",
  "scene_count": 2,
  "credits_used": 121,
  "credits_remaining": 4079,
  "model": "veo-3.1-fast",
  "image_model": "nano-banana-pro",
  "cost_per_scene": 55
}

Error Responses

StatusCause
400Invalid prompt (no scenes/dialogues), bad character URL, or unsupported base64 image format
400Too many scenes (max 20)
400No character source — neither character_image_url, character_image_base64, nor a === CHARACTER === ... block in the prompt
400Unknown model value — response includes allowed_models array
400Unknown image_model value — response includes allowed_image_models array
402Insufficient credits
413character_image_base64 exceeds the ~20 MB cap

Check Script Runner Status

GET /api/v1/partner/script-runner/<job_id>

Poll until status is complete, partial (some scenes failed, others succeeded), or error. Recommended poll interval: 5–10 seconds.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-8e7d6c5b",
  "name": "My Run",
  "status": "complete",
  "scene_count": 2,
  "scenes_complete": 2,
  "scenes": [
    {
      "name": "Living Room",
      "status": "complete",
      "setting": "Modern apartment, afternoon light, sofa.",
      "dialogue": "I used to dread mornings...",
      "start_image_url": "https://knightvision.tech/static/generated_images/srwk_12345.png",
      "video_url": "https://knightvision.tech/static/generated_videos/kv-1f2e3d4c.mp4"
    },
    {
      "name": "Kitchen",
      "status": "complete",
      "setting": "Bright kitchen, character holding a coffee mug.",
      "dialogue": "My friends keep asking me what I changed.",
      "start_image_url": "https://knightvision.tech/static/generated_images/srwk_67890.png",
      "video_url": "https://knightvision.tech/static/generated_videos/kv-5b6a7f8e.mp4"
    }
  ],
  "credits_charged": 121,
  "credits_refunded": 0,
  "model": "veo-3.1-fast",
  "image_model": "nano-banana-pro",
  "cost_per_scene": 55,
  "error": null,
  "created_at": "2026-04-20 08:12:03",
  "updated_at": "2026-04-20 08:18:51"
}

While a scene is being rendered, its status is processing and start_image_url / video_url may be absent. If an individual scene fails, the scene-level status is error with an error field, and the run-level credits_refunded reflects the cost_per_scene credits returned per failed scene.

List Script Runner Runs

GET /api/v1/partner/script-runner

Paginated list of the calling partner's Script Runner runs, sorted newest first.

Query Parameters

ParameterTypeDefaultDescription
limitinteger20Max 100
offsetinteger0Pagination offset

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "total": 12,
  "limit": 20,
  "offset": 0,
  "runs": [
    {"job_id": "kv-8e7d6c5b", "partner_job_id": null, "name": "My Run", "model_used": "veo-3.1-fast", "image_model_used": "nano-banana-pro", "status": "complete", "scene_count": 2, "credits_charged": 121, "credits_refunded": 0, "created_at": "2026-04-20 08:12:03", "updated_at": "2026-04-20 08:18:51"}
  ]
}

Voiceover (TTS)

Synthesize speech via ElevenLabs. Synchronous — the response contains the final audio URL. The Turbo models answer within a few seconds; eleven_v4 and eleven_v3 take longer on long scripts. A request times out after 60 s on eleven_v4_turbo / eleven_turbo_v2_5 and after 90 s on eleven_v4 / eleven_v3; a timeout is refunded. Pair with Video Generation to add narration to AI clips.

List Voices

GET /api/v1/partner/voiceover/voices

Return the curated voice catalog plus available TTS models. Use these voice_id and model_id values when calling /generate-voiceover.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "voices": [
    {
      "voice_id": "EXAVITQu4vr4xnSDxMaL",
      "name": "Bella",
      "lang": "EN",
      "gender": "female",
      "description": "Warm, professional — brand narration"
    }
  ],
  "models": [
    {"id": "eleven_v4_turbo", "label": "v4 Turbo"},
    {"id": "eleven_v4", "label": "v4"},
    {"id": "eleven_turbo_v2_5", "label": "Turbo v2.5"},
    {"id": "eleven_v3", "label": "v3"}
  ],
  "default_model": "eleven_v4_turbo"
}

Example

curl https://knightvision.ai/api/v1/partner/voiceover/voices \
  -H "Authorization: Bearer kv_partner_your_key_here"

Generate Voiceover

POST /api/v1/partner/generate-voiceover

Synchronous TTS. On any upstream failure (HTTP ≥400, timeout, network error), credits are automatically refunded.

Request

{
  "text": "Tired of bloated SaaS? Try the lean alternative.",
  "voice_id": "EXAVITQu4vr4xnSDxMaL",
  "model_id": "eleven_v4_turbo",
  "output_format": "mp3_44100_128"
}
FieldTypeRequiredDefaultDescription
textstringyes—Text to synthesize. Max 5000 characters.
voice_idstringyes—One of the voice_id values from /voiceover/voices.
model_idstringno"eleven_v4_turbo""eleven_v4_turbo" (expressive, fast), "eleven_v4" (highest quality, slower), "eleven_turbo_v2_5" or "eleven_v3". Since 2026-10-03 the default is eleven_v4_turbo; send model_id explicitly to pin a model.
output_formatstringno"mp3_44100_128"ElevenLabs audio format (e.g. mp3_44100_128).
sourcestringno""Free-text label for your own bookkeeping (returned by /voiceovers).

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "id": 42,
  "job_id": "kv-9c8b7a65",
  "audio_url": "https://knightvision.tech/static/generated_voiceovers/vo_42_a1b2c3d4.mp3",
  "voice_id": "EXAVITQu4vr4xnSDxMaL",
  "voice_name": "Bella",
  "model_id": "eleven_v4_turbo",
  "char_count": 47,
  "credits_used": 2,
  "credits_remaining": 998
}

Price: 1 credit per 80 characters (v4 Turbo, Turbo v2.5), per 40 characters (v4) or per 50 characters (v3) on the standard basis, minimum 1 — then + 10 % rounded up per job on api_plus10, so the minimum charge is 2 credits (47 characters above).

Errors

StatusError
400"Please provide text to synthesize"
400"Text exceeds 5000-character limit"
400"Invalid voice_id" / "Invalid model_id"
402"Insufficient credits. Need {cost} credits." (includes credits_remaining)
429"Too many in-flight jobs for this partner. Wait for some to finish and retry."
502"Voiceover service unavailable. Credits refunded." (network / timeout)
502"Voiceover generation failed. Credits refunded." (HTTP ≥400 from TTS provider)
503"Voiceover service is not configured."

audio_url is permanent and does not expire — download or hotlink as needed.

Example

curl -X POST https://knightvision.ai/api/v1/partner/generate-voiceover \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world.", "voice_id": "EXAVITQu4vr4xnSDxMaL", "model_id": "eleven_v4_turbo"}'

List Voiceovers

GET /api/v1/partner/voiceovers

List voiceovers your account has generated, newest first.

Query Parameters

ParameterTypeDefaultDescription
limitinteger30Max 200
offsetinteger0Pagination offset

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "voiceovers": [
    {
      "id": 42,
      "job_id": "kv-9c8b7a65",
      "text": "Hello world.",
      "voice_id": "EXAVITQu4vr4xnSDxMaL",
      "voice_name": "Bella",
      "model_id": "eleven_v4_turbo",
      "char_count": 12,
      "credits_used": 2,
      "audio_url": "https://knightvision.tech/static/generated_voiceovers/vo_42_a1b2c3d4.mp3",
      "status": "success",
      "error_message": null,
      "source": "",
      "created_at": "2026-05-09 12:00:00"
    }
  ],
  "total": 1,
  "limit": 30,
  "offset": 0
}

audio_url is null when status != "success". error_message is sanitized — provider names are never exposed.

Example

curl "https://knightvision.ai/api/v1/partner/voiceovers?limit=10" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Music (Suno)

Generate original music or extend an existing audio source. Both operations are asynchronous: submit a job, poll the shared status endpoint, and recover recent jobs through the music list. Models: V5_5 (default) and V5.

Generate Music

POST /api/v1/partner/generate-music

Start a music-generation job. Returns a job_id immediately (HTTP 202); poll /music-status/<job_id> until it completes. Filling style and title enables custom mode (the prompt becomes the sung lyrics unless instrumental is true); leave both empty for simple mode (prompt is a short description and lyrics are written automatically).

Request

{
  "prompt": "an upbeat synthwave track with driving drums and a hopeful mood",
  "model": "V5_5",
  "instrumental": false,
  "style": "Synthwave",
  "title": "Midnight Drive"
}
FieldTypeRequiredDefaultDescription
promptstringconditional—Simple mode: a description (max 500 chars), required. Custom mode: the lyrics (max 5000 chars), required unless instrumental is true.
modelstringno"V5_5""V5_5" or "V5".
instrumentalbooleannofalseGenerate a track with no vocals.
stylestringconditional—Genre/mood (max 1000 chars). Required with title when either is set (custom mode).
titlestringconditional—Track title (max 80 chars). Required with style when either is set.
partner_job_idstringno—Your own cross-reference, echoed back.

Response (202)

{
  "request_id": "a1b2c3d4e5f6",
  "id": 42,
  "job_id": "kv-c4d5e6f7",
  "partner_job_id": null,
  "status": "queued",
  "model": "V5_5",
  "credits_used": 11,
  "credits_remaining": 989
}

Price: 11 credits per generation on api_plus10 (standard basis 10); one generation returns two track variations.

Errors

StatusError
400"Please provide a prompt describing the music." / limit + custom-mode validation messages
402"Insufficient credits. Need {cost} credits." (includes credits_remaining)
429"Too many in-flight jobs for this partner. Wait for some to finish and retry."
502"Music generation failed to start. Credits refunded."
503"Music generation is not configured."

Example

curl -X POST https://knightvision.ai/api/v1/partner/generate-music \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a calm lo-fi beat to study to", "model": "V5_5"}'

Extend Music

POST /api/v1/partner/extend-music

Extend either a completed music track owned by your Partner API account or an audio file sent with the request. The server selects the available extension path and returns it as operation: "extend" when the referenced track can be extended directly, otherwise "upload_extend". The job is asynchronous and uses the same status and list endpoints as generated music.

Required idempotency header: send Idempotency-Key: <key> with every request. A key must contain 8–100 letters, numbers, underscores, or hyphens. Retrying the same extension parameters (and, for multipart, the same file bytes) returns the existing job without another charge. Reusing the key with different extension parameters returns HTTP 409; the partner_job_id recorded by the first accepted request is retained on replay. The JSON response field request_id is a server-generated trace ID and must not be sent in the request body.

Option A: extend an owned music track (JSON)

The source must be a completed, unpruned music job owned by the authenticated partner. track selects a variation and defaults to 0. Provider IDs, filesystem paths, URLs, and another partner's job IDs are never accepted as source capabilities. If a stored track cannot use the direct path, Knightvision securely restages its local audio; that fallback has the same 8-minute source limit as multipart Upload-and-Extend. Include a prompt in original mode so the request is valid for either path.

{
  "source": {
    "kind": "kv_music",
    "ref": "kv-a1b2c3d4",
    "track": 0
  },
  "continue_at": 42.5,
  "mode": "original",
  "prompt": "continue with a restrained guitar solo",
  "partner_job_id": "catalog-track-184"
}
curl -X POST https://knightvision.ai/api/v1/partner/extend-music \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Idempotency-Key: music_extend_184_take_1" \
  -H "Content-Type: application/json" \
  -d '{"source":{"kind":"kv_music","ref":"kv-a1b2c3d4","track":0},"continue_at":42.5,"mode":"original","prompt":"continue with a restrained guitar solo","partner_job_id":"catalog-track-184"}'

Option B: upload and extend audio (multipart)

Send one audio file and a parameters form field containing a JSON object. Accepted filename extensions are AAC, FLAC, M4A, MP3, OGG, and WAV. The file must contain a readable audio stream, be at most 95,000,000 bytes, and be no longer than 8 minutes. URLs and base64 audio are not accepted.

curl -X POST https://knightvision.ai/api/v1/partner/extend-music \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Idempotency-Key: upload_extend_20260816_001" \
  -F "audio=@./source-track.wav" \
  -F 'parameters={"continue_at":38.0,"mode":"custom","model":"V5_5","prompt":"instrumental bridge, then return to the main theme","style":"cinematic electronic","title":"Extended Theme","instrumental":true,"partner_job_id":"upload-extend-001"}'

Controls

For JSON requests, these fields live in the request body. For multipart requests, put them in the parameters JSON object; do not include source there.

FieldTypeRequiredDefaultDescription
sourceobjectJSON only—{"kind":"kv_music","ref":"<owned job_id>","track":0}. track is a non-negative integer.
continue_atnumberyes—Extension point in seconds. Must be greater than 0 and less than the source duration.
modestringno"original""original" or "custom".
modelstringno"V5_5""V5_5" or "V5". An owned music source inherits its model; if supplied, the value must match that source.
promptstringconditional""Max 5000 characters. Required for original-mode Upload-and-Extend, for every direct custom extension, and for a custom vocal upload. A direct original extension may omit it; a custom instrumental upload may omit it.
stylestringcustom mode""Genre, arrangement, or mood; max 1000 characters. Custom mode requires both style and title.
titlestringcustom mode""Output title; max 80 characters. Custom mode requires both title and style.
instrumentalbooleannofalseRequest output without vocals. An owned music source inherits its setting; if supplied, the value must match that source.
negative_tagsstringno—Elements to avoid; max 1000 characters.
vocal_genderstringno—"m" or "f".
style_weightnumberno—0–1, with at most two decimal places.
weirdness_constraintnumberno—0–1, with at most two decimal places.
audio_weightnumberno—0–1, with at most two decimal places.
persona_idstringno—Custom mode only; max 200 characters.
persona_modelstringno—Custom mode only; max 100 characters.
partner_job_idstringno—Your non-unique cross-reference: 1–64 letters, numbers, underscores, or hyphens. Persisted and returned by submit, status, and list responses.

Response (202 — new job)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-e5f6a7b8",
  "partner_job_id": "catalog-track-184",
  "status": "queued",
  "operation": "extend",
  "model": "V5_5",
  "credits_used": 11,
  "credits_remaining": 989,
  "idempotency_key": "music_extend_184_take_1"
}

Response (200 — identical retry)

{
  "request_id": "f6e5d4c3b2a1",
  "job_id": "kv-e5f6a7b8",
  "partner_job_id": "catalog-track-184",
  "status": "queued",
  "operation": "extend",
  "model": "V5_5",
  "credits_used": 11,
  "credits_remaining": 989,
  "idempotency_key": "music_extend_184_take_1",
  "deduplicated": true
}

An identical retry reports the job's current state as "queued", "complete", or "error".

credits_used is the authoritative partner-tier price: 11 credits on api_plus10 (standard basis 10). A failed job is refunded automatically; the 502 body then also carries a machine-readable error_code.

Errors

StatusMeaning
400Missing/invalid Idempotency-Key, invalid controls or source, unreadable/non-audio upload, invalid partner_job_id, or continue_at outside the source.
402Insufficient credits; includes credits_remaining.
404The referenced music job or variation is unavailable, incomplete, pruned, or not owned by your partner account.
409The Idempotency-Key was already used with different parameters or different upload bytes.
413The multipart audio file exceeds 95,000,000 bytes.
415The content type is neither application/json nor multipart/form-data.
429Endpoint rate limit or partner in-flight-job limit reached.
502The extension could not be submitted after billing; credits are refunded.
503Music generation is unavailable or the server could not process the uploaded file.

Check Music Status

GET /api/v1/partner/music-status/<job_id>

Poll a generated or extended music job owned by your partner account. While work is active, status is "processing"; on success it is "complete" with a tracks array; on failure it is "error" with a sanitized message. operation is "generate", "extend", or "upload_extend".

Response (200 — complete)

{
  "request_id": "a1b2c3d4e5f6",
  "job_id": "kv-e5f6a7b8",
  "partner_job_id": "catalog-track-184",
  "status": "complete",
  "operation": "extend",
  "model": "V5_5",
  "credits_used": 11,
  "tracks": [
    {
      "audio_url": "https://knightvision.tech/static/generated_music/kv-e5f6a7b8_0.mp3",
      "image_url": "https://knightvision.tech/static/generated_music/kv-e5f6a7b8_0.jpg",
      "title": "Extended Theme",
      "duration": 201.1
    }
  ]
}

Completed track URLs are served by Knightvision. On "status": "error" the body carries a sanitized error plus a machine-readable error_code (for example provider_down, timeout, safety_filter, input_rejected) — the music endpoints are the only ones that expose error_code. Download output you need to keep; generated media remains subject to Knightvision's retention and storage-pressure cleanup.

Example

curl "https://knightvision.ai/api/v1/partner/music-status/kv-e5f6a7b8" \
  -H "Authorization: Bearer kv_partner_your_key_here"

List Music

GET /api/v1/partner/music

List generated and extended music jobs owned by your partner account, newest first. Use this endpoint to recover a job_id through your persisted partner_job_id after a lost submit response. Completed rows include public tracks; active and failed rows return an empty tracks array.

Query Parameters

ParameterTypeDefaultDescription
limitinteger301–200 rows.
offsetinteger0Non-negative pagination offset.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "music": [
    {
      "job_id": "kv-e5f6a7b8",
      "partner_job_id": "catalog-track-184",
      "status": "complete",
      "operation": "extend",
      "model": "V5_5",
      "title": "Extended Theme",
      "credits_used": 11,
      "tracks": [
        {
          "audio_url": "https://knightvision.tech/static/generated_music/kv-e5f6a7b8_0.mp3",
          "image_url": "https://knightvision.tech/static/generated_music/kv-e5f6a7b8_0.jpg",
          "title": "Extended Theme",
          "duration": 201.1
        }
      ],
      "created_at": "2026-08-16 12:00:00",
      "completed_at": "2026-08-16 12:03:00"
    }
  ],
  "total": 1,
  "limit": 30,
  "offset": 0
}

The list exposes only partner-safe media fields. Internal provider identifiers, temporary staging URLs, local paths, and idempotency keys are not returned.

Example

curl "https://knightvision.ai/api/v1/partner/music?limit=30&offset=0" \
  -H "Authorization: Bearer kv_partner_your_key_here"

Account

Inspect your credit balance, usage, account info, and full transaction history.

Check Credits

GET /api/v1/partner/credits

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "credits_remaining": 950,
  "pricing_tier": "api_plus10",
  "credit_cost_per_analysis": 50,
  "costs": {
    "analysis": 50,
    "text_analysis": 5,
    "captions": 25,
    "image_per_image": 15,
    "image_per_image_2k": 15,
    "image_per_image_4k": 22,
    "image_models": {
      "nano-banana-pro": {
        "2K": 15,
        "4K": 22
      },
      "gpt-image-2": {
        "1K": 19,
        "2K": 24,
        "4K": 29
      },
      "gpt-image-2-5-flare": {
        "1K": 22,
        "2K": 28,
        "4K": 34
      },
      "gpt-image-2-5-sunburst": {
        "1K": 22,
        "2K": 28,
        "4K": 34
      }
    },
    "video": {
      "kling-v3-text-to-video": {
        "per_clip": true,
        "combos": {
          "720p_5s_noaudio": 50,
          "720p_10s_noaudio": 100,
          "1080p_5s_noaudio": 65,
          "1080p_10s_noaudio": 150,
          "720p_5s_audio": 75,
          "720p_10s_audio": 145,
          "1080p_5s_audio": 100,
          "1080p_10s_audio": 200
        }
      },
      "kling-v3-image-to-video": {
        "per_clip": true,
        "combos": {
          "720p_5s_noaudio": 50,
          "720p_10s_noaudio": 100,
          "1080p_5s_noaudio": 65,
          "1080p_10s_noaudio": 150,
          "720p_5s_audio": 75,
          "720p_10s_audio": 145,
          "1080p_5s_audio": 100,
          "1080p_10s_audio": 200
        }
      },
      "kling-v3-motion-control": {
        "per_second": true,
        "720p": 14,
        "1080p": 19
      },
      "seedance-2-direct": {
        "per_second": true,
        "480p": 14,
        "720p": 29,
        "1080p": 72,
        "4K": 146,
        "with_reference_video": {
          "basis": "reference_plus_output_seconds",
          "480p": 9,
          "720p": 18,
          "1080p": 44,
          "4K": 90
        }
      },
      "seedance-2-5": {
        "per_second": true,
        "480p": 20,
        "720p": 45,
        "1080p": 111,
        "with_reference_video": {
          "basis": "reference_plus_output_seconds",
          "480p": 12,
          "720p": 27,
          "1080p": 67
        }
      },
      "veo-3.1-quality": {
        "per_second": false,
        "flat": {
          "720p": 150,
          "1080p": 178,
          "4K": 200
        }
      },
      "veo-3.1-fast": {
        "per_second": false,
        "flat": {
          "720p": 41,
          "1080p": 49,
          "4K": 120
        }
      }
    },
    "animation": 156,
    "merge_videos": 100,
    "omni": {
      "character_creation": 30,
      "voice_creation": 0,
      "video_price": {
        "1080p": {
          "4": 120,
          "6": 130,
          "8": 140,
          "10": 150
        },
        "4k": {
          "4": 180,
          "6": 190,
          "8": 200,
          "10": 210
        }
      },
      "models": [
        "omni",
        "omni-1.1"
      ]
    },
    "voiceover": {
      "models": {
        "eleven_v4_turbo": {
          "chars_per_credit": 80,
          "label": "v4 Turbo"
        },
        "eleven_v4": {
          "chars_per_credit": 40,
          "label": "v4"
        },
        "eleven_turbo_v2_5": {
          "chars_per_credit": 80,
          "label": "Turbo v2.5"
        },
        "eleven_v3": {
          "chars_per_credit": 50,
          "label": "v3"
        }
      },
      "default_model": "eleven_v4_turbo",
      "min_credits": 1,
      "max_text_length": 5000
    },
    "music": {
      "models": {
        "V5_5": 10,
        "V5": 10
      },
      "default_model": "V5_5",
      "flat_per_generation": 10,
      "tracks_per_generation": 2
    },
    "upscale": {
      "image": {
        "per_image_by_output_tier": {
          "2K": 7,
          "4K": 14,
          "8K": 28
        },
        "tier_max_long_edge": {
          "2K": 2048,
          "4K": 4096,
          "8K": 8192
        },
        "factors": [
          1,
          2,
          4
        ],
        "input_max_bytes": 10000000,
        "max_output_long_edge": 8192
      },
      "video": {
        "per_second_by_factor": {
          "1": 6,
          "2": 6,
          "4": 10
        },
        "basis": "ceil(input_seconds)",
        "factors": [
          1,
          2,
          4
        ],
        "input_max_bytes": 50000000,
        "input_max_seconds": 120,
        "max_output_long_edge": 4096
      }
    }
  }
}

The costs object is the standard basis for every account and is the source the tables on this page are derived from; pricing_tier tells you how your account is billed from it (see Pricing tiers). Your tier is applied when a job is submitted; the authoritative charged amount is always the credits_used of the submit response (for example, api_plus10 charges 28 for Captions and 11 for a music generate or extend job). Per-second video entries are credits per second; combos are flat per-clip prices keyed <quality>_<duration>s_<audio|noaudio>.

Example

curl https://knightvision.ai/api/v1/partner/credits \
  -H "Authorization: Bearer kv_partner_your_key_here"

Account Info

GET /api/v1/partner/me

Return the calling partner's account info and live credit balance. Free to call.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "name": "Acme Corp",
  "api_key_prefix": "kv_partner_abc12345",
  "pricing_tier": "api_plus10",
  "credits_remaining": 4200,
  "is_active": true,
  "created_at": "2026-02-01 10:00:00",
  "last_active": "2026-04-20 09:15:33"
}

The raw API key is never returned — only kv_partner_ plus the first 8 characters, so you can identify which key is calling. pricing_tier is api_plus10, standard, or custom (see Pricing tiers).

Usage Aggregates

GET /api/v1/partner/usage

Per-endpoint call counts and credit consumption for the calling partner.

Query Parameters

ParameterTypeDefaultDescription
sinceISO 8601 dateall timeFilter calls made on or after this timestamp

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "since": "2026-04-01T00:00:00",
  "credits_remaining": 4200,
  "total_calls": 312,
  "total_credits_used": 5840,
  "per_endpoint": [
    {"endpoint": "/api/v1/partner/generate-video", "calls": 42, "credits_used": 4200},
    {"endpoint": "/api/v1/partner/analyze", "calls": 28, "credits_used": 1400}
  ]
}

Credit Transactions

GET /api/v1/partner/transactions

Paginated credit-ledger entries (usage, refunds, purchases, adjustments). Sorted newest first.

Query Parameters

ParameterTypeDefaultDescription
limitinteger50Max 200
offsetinteger0Pagination offset

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "total": 178,
  "limit": 50,
  "offset": 0,
  "transactions": [
    {"amount": -121, "type": "usage", "description": "Script Runner (2 scenes): My Run", "created_at": "2026-04-20 08:12:03"},
    {"amount": 55, "type": "refund", "description": "Refund: Script Runner scene failures (kv-8e7d6c5b)", "created_at": "2026-04-20 08:18:51"}
  ]
}

Typical Workflows

Ad Analysis

1. POST /api/v1/partner/analyze        → get job_id
2. GET  /api/v1/partner/status/<job_id> → poll every 3-5s until "complete" or "error"
3. Read result.summary, result.analysis, result.transcription, result.ugc_script

Image Generation

1. POST /api/v1/partner/generate-image                → get generation_id(s)
2. GET  /api/v1/partner/image-status/<generation_id>  → poll every 3-5s until "success" or "failed"
3. Download image from image_url

Video Generation (with consistent character)

1. POST /api/v1/partner/generate-image    → create reference image of your character
2. Wait for image_url
3. POST /api/v1/partner/generate-video    → pass image_url + image-to-video model
4. GET  /api/v1/partner/video-status/<job_id> → poll until "complete"
5. Download video from video_url

Captions for a Generated Video

1. Complete a Partner API video job
2. POST /api/v1/partner/captions                 → pass its video_job_id; get Caption job_id
3. GET  /api/v1/partner/captions/<job_id>        → poll until complete, no_speech, or failed
4. Download captioned_url promptly (normal 14-day window; storage pressure may prune sooner)
5. Optional: GET /captions/<job_id>/chunks, correct text, POST /captions/<job_id>/edit, then poll again

Captions for a Large Upload

1. POST /api/v1/partner/captions/uploads/init     → get session_id, chunk_size, part_count
2. POST each raw part to /captions/uploads/<session_id>/part/<index>
3. POST /captions/uploads/<session_id>/finalize  → the only credit deduction; get Caption job_id
4. GET  /api/v1/partner/captions/<job_id>        → poll to a terminal status

Script Runner

1. POST /api/v1/partner/script-runner            → submit script + character_image_url
2. GET  /api/v1/partner/script-runner/<job_id>   → poll every 5-10s until "complete" or "partial"
3. Iterate scenes[].video_url for the rendered clips

Extend Music

1. Choose a completed partner-owned music job, or an audio file up to 95,000,000 bytes / 8 minutes
2. POST /api/v1/partner/extend-music with a unique Idempotency-Key header → get job_id
3. GET  /api/v1/partner/music-status/<job_id>                        → poll until "complete" or "error"
4. Download the returned tracks
5. If the submit response was lost, GET /api/v1/partner/music and match partner_job_id

Voiceover (TTS)

1. (optional) GET /api/v1/partner/voiceover/voices  → list voice_ids + models
2. POST /api/v1/partner/generate-voiceover          → returns audio_url synchronously
3. Download or hotlink audio_url

Webhooks

Instead of polling, let us call you: we POST one signed JSON event to your URL when a video or Omni job completes or fails. Set a default URL on your account (dashboard or POST /webhook), or pass callback_url on an individual POST /generate-video, POST /generate-animation or POST /omni/video request to override it for that job. The URL must be public https.

Events

EventWhen
video.completedA /generate-video or /generate-animation job finished; data.video_url is ready
video.failedThe job failed; credits were refunded (data.credits_refunded)
omni_video.completedAn /omni/video job finished
omni_video.failedThe Omni job failed; credits were refunded
webhook.testSent by POST /webhook/test or the dashboard button

Payload

data carries the same fields as the job's status endpoint. The body is frozen when the event is created and re-sent byte for byte on every retry, so id is stable: treat an event id you have already processed as a duplicate.

POST https://example.com/knightvision/webhook
Content-Type: application/json
User-Agent: Knightvision-Webhooks/1.0
X-Knightvision-Event: video.completed
X-Knightvision-Event-Id: evt_3f9c2a7b1d0e4c55
X-Knightvision-Delivery-Attempt: 1
X-Knightvision-Signature: t=1760380800,v1=5d41402abc4b2a76b9719d911017c592e3a1f0c9b8d7e6f5a4b3c2d1e0f9a8b7

{
  "id": "evt_3f9c2a7b1d0e4c55",
  "event": "video.completed",
  "created_at": 1760380800,
  "data": {
    "job_id": "kv-7b2d9e10",
    "partner_job_id": "order-42-clip-07",
    "status": "complete",
    "model": "kling-v3-image-to-video",
    "duration": 5,
    "aspect_ratio": "9:16",
    "credits_used": 77,
    "credits_refunded": 0,
    "video_url": "https://knightvision.ai/static/generated_videos/kv-7b2d9e10.mp4",
    "error": null,
    "created_at": "2026-10-13 18:00:00"
  }
}

Verifying the signature

Every delivery is signed with your webhook secret (shown in the dashboard and by GET /webhook). v1 is HMAC-SHA256(secret, "<t>.<raw body>") in hex. Compute it over the raw request body, compare with a constant-time comparison and reject timestamps older than five minutes.

# Python
import hmac, hashlib, time

def verify(secret: str, header: str, raw_body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = int(parts["t"]), parts["v1"]
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)
// Node.js
const crypto = require("crypto");

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Delivery and retries

Respond with any 2xx within 10 seconds; the response body is ignored. Redirects are not followed. Anything else (non-2xx, timeout, connection error) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, six attempts in total, then the delivery is marked failed. Events are sent a few seconds after the job reaches its final state. Deliveries are independent and may arrive out of order; use data.job_id and the event id, not the arrival order.

Get Webhook Settings

GET /api/v1/partner/webhook

Your default URL, signing secret and the retry policy.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "url": "https://example.com/knightvision/webhook",
  "secret": "whsec_9f8e7d6c5b4a39281706f5e4d3c2b1a0f9e8d7c6b5a49382",
  "events": ["video.completed", "video.failed", "omni_video.completed", "omni_video.failed", "webhook.test"],
  "max_attempts": 6,
  "retry_delays_seconds": [60, 300, 1800, 7200, 21600],
  "signature_header": "X-Knightvision-Signature"
}

Set Webhook URL

POST /api/v1/partner/webhook

Set the account default (public https), clear it with null, or rotate the signing secret. The secret is created together with the first URL.

Request Body

FieldTypeRequiredDescription
urlstring or nullnoDefault callback URL; null or "" clears it. Omit to leave unchanged.
rotate_secretbooleannotrue replaces the signing secret; deliveries from then on use the new one.

Example

curl -X POST "https://knightvision.ai/api/v1/partner/webhook" \
  -H "Authorization: Bearer kv_partner_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/knightvision/webhook"}'

Errors

StatusReason
400url is not a public https URL (private addresses, userinfo, non-ASCII hosts and plain http are rejected)

Send Test Event

POST /api/v1/partner/webhook/test

Sends one signed webhook.test event synchronously to your default URL (or to url in the body) and reports what your endpoint answered. Not retried.

Request Body

FieldTypeRequiredDescription
urlstringnoSend to this URL instead of the account default.

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "delivered": true,
  "status_code": 200,
  "error": null,
  "event_id": "evt_3f9c2a7b1d0e4c55",
  "url": "https://example.com/knightvision/webhook"
}

List Deliveries

GET /api/v1/partner/webhook/deliveries

Your recent deliveries, newest first, with the outcome of the last attempt.

Query Parameters

ParamTypeDefaultDescription
limitinteger50Max results (1-200)
job_idstring—Only deliveries for this job

Response (200)

{
  "request_id": "a1b2c3d4e5f6",
  "deliveries": [
    {
      "event_id": "evt_3f9c2a7b1d0e4c55",
      "event": "video.completed",
      "job_id": "kv-7b2d9e10",
      "url": "https://example.com/knightvision/webhook",
      "status": "delivered",
      "attempt": 1,
      "max_attempts": 6,
      "last_status_code": 200,
      "last_error": null,
      "created_at": 1760380800,
      "last_attempt_at": 1760380801,
      "next_attempt_at": null,
      "delivered_at": 1760380801
    }
  ],
  "limit": 50
}

Error Handling

All errors return a JSON object with an error field (a sanitized, human-readable string; provider names are never exposed). A feature that is switched off for your account answers 403 {"error": "Feature '<key>' is not enabled for this partner.", "feature": "<key>"}. Only the music endpoints add a machine-readable error_code. HTTP status codes:

CodeMeaning
400Bad request (invalid input, missing fields)
401Invalid or missing API key
402Insufficient credits (includes credits_remaining)
403API key deactivated or the requested feature is disabled for this partner
404Resource not found or not owned by your account
409Resource state conflict (for example an incomplete source video, missing upload parts, an edit already running, or an idempotency key reused with different parameters)
410Source media required for a Caption start or re-burn has been pruned
413Payload too large (base64 image cap, 95 MB single Caption/music upload, or 1 GB chunked Caption upload)
415Unsupported content type for an endpoint with explicit JSON or multipart contracts
429Rate limit exceeded OR too many in-flight async jobs for your account (wait at least 10s, then retry)
500Server-side error
502Upstream provider unreachable — a charged job is refunded
503Service temporarily unavailable (voiceover / music not configured, upload processing, or Omni offline)
Status polling endpoints return HTTP 200 even when the job itself has failed. Always check the status field in the response body. Successful submits answer 202 for async jobs, 200 for synchronous ones, and 201 for a created character.

Payload & Concurrency Limits

LimitValueEndpoint(s)
In-flight async jobs per account10 (default)POST /analyze, POST /generate-video, POST /generate-animation, POST /omni/video, POST /omni/script-runner, POST /script-runner, POST /generate-voiceover, POST /generate-music — a job holds its slot until it finishes. Not counted: /extend-music, /generate-image, /upscale-image, /upscale-video, /merge-videos, character creation and /omni/improve-prompt
Active Caption jobs per account10 (default; partner-specific setting)POST /captions, POST /captions/upload, POST /captions/uploads/<session_id>/finalize, POST /captions/<job_id>/edit
Caption single multipart upload95,000,000 bytescaptions/upload
Music extension multipart upload95,000,000 bytes; source duration up to 8 minutesextend-music
Caption chunked upload1,000,000,000 bytes, 30 mincaptions/uploads/*
Open Caption upload sessions per account2captions/uploads/init
image_base64 max size~20 MB binary (~28M base64 chars)generate-video
ref_images[i].base64 max per image~20 MB binary (~28M base64 chars)generate-image
ref_images[] combined max~72 MB binary (~100M base64 chars)generate-image
ref_images + reference_image_urls count5 referencesgenerate-image
reference_image_urls + reference_images_base649 references (seedance-2-direct) / 30 (seedance-2-5); ~20 MB binary each, ~72 MB combinedgenerate-video (Seedance)
reference_video_urls3 clips, 2–15 s each and in total (seedance-2-direct) / 10 clips, 2–30 s each and in total (seedance-2-5); public HTTPS MP4/MOV, max 95 MB each, downloaded and measured server-sidegenerate-video (Seedance)
reference_audio_urls3 files, 2–15 s each and 15 s in total, only with a reference image or video (seedance-2-direct) / 10 files, 2–30 s each and 30 s in total (seedance-2-5); public HTTPS MP3/WAV/M4A, max 50 MB each, downloaded, measured and converted to MP3 server-sidegenerate-video (Seedance)
image_base64 / image_url source (upscale)~20 MB base64 payload; decoded image ≤ 10,000,000 bytes, JPEG/PNG/WebP; output long edge ≤ 8,192 pxupscale-image
video_url source (upscale)≤ 50,000,000 bytes, ≤ 120 s, MP4/MOV, downloaded and measured server-side; output long edge ≤ 4,096 pxupscale-video
character_image_base64 max size~20 MB binary (~28M base64 chars)script-runner
sections[].image_base64 max per image / combined~20 MB binary each, ~72 MB combinedomni/script-runner
Hostnames in URL fieldsASCII only, no userinfo@hostanalyze, generate-video image_url, script-runner character_image_url
If you see 429 Too many in-flight jobs, your account is at the concurrency cap. Wait for some jobs to finish (poll their status endpoints) and retry. Need a higher cap? Open a support ticket in the Knightvision Discord and we raise it for your account, see Rate Limits.

Changelog

DateChange
2026-10-13Webhooks: one signed JSON event per finished or failed video / Omni job instead of polling. Set a default URL with POST /webhook (or in the dashboard) or pass callback_url on POST /generate-video, POST /generate-animation and POST /omni/video; verify with X-Knightvision-Signature, six delivery attempts over about ten hours, GET /webhook/deliveries shows the outcome. GET /videos and GET /omni/videos take status and since and up to 200 results per page. See Webhooks.
2026-10-12Higher limits on request: the in-flight job cap and the per-minute limits on POST /generate-video, POST /omni/video and POST /generate-animation can be raised per account. Open a support ticket in the Knightvision Discord with your account name and the throughput you need; there is no charge. The defaults in the tables above are unchanged.
2026-10-03Voiceover v4: POST /generate-voiceover accepts the new model_id values eleven_v4_turbo (1 credit per 80 characters standard basis, like Turbo v2.5) and eleven_v4 (1 credit per 40 characters). eleven_v4_turbo is now the default when model_id is omitted (same price as the previous default); eleven_turbo_v2_5 and eleven_v3 stay available. The provider timeout is 60 s on the Turbo models and 90 s on eleven_v4 / eleven_v3; GET /voiceover/voices and GET /credits list all four models.
2026-10-02Metadata removal for videos: POST /remove-metadata now also takes video_url (MP4 or MOV, max 100 MB) and returns a cleaned copy without the C2PA Content Credentials manifest, container and stream tags, chapters and non-picture/non-sound tracks; the video and audio streams are copied unchanged. Free, with its own daily quota (50 per 24 hours). POST /generate-video and POST /omni/video accept strip_metadata: true; /video-status, /videos, /omni/video/<job_id> and /omni/videos report metadata_removed.
2026-10-01Metadata removal: POST /remove-metadata removes the AI-provenance declaration (C2PA Content Credentials) plus the EXIF, XMP and IPTC metadata from an existing JPEG, PNG or WebP. It is lossless, synchronous and free, with a daily quota. On POST /generate-image, strip_metadata: true delivers the finished images without that metadata in the first place; GET /image-status and GET /images report the outcome as metadata_removed.
2026-09-28Reference audio on /generate-video for seedance-2-direct and seedance-2-5: pass public HTTPS MP3/WAV/M4A URLs as reference_audio_urls (3 files / 15 s total on 2.0, where at least one reference image or video is required; 10 files / 30 s total on 2.5, where audio alone works). Every file is downloaded, measured and converted to MP3 before credits are charged; reference audio does not change the price. Name the files in your prompt as [Audio 1], [Audio 2].
2026-09-25Omni voices: build a reusable voice on one of the preset voices (GET /omni/voice-presets) with POST /omni/voices (free; list with GET, remove with DELETE /omni/voices/<voice_id>). Bake one into a character with voice_id on POST /omni/characters, or add up to 3 to a clip with voice_ids on POST /omni/video. Voices are outside the 7-reference budget and do not change a video's price; GET /omni/video/<job_id> returns voice_ids, character responses return voice_id / voice, and GET /credits lists costs.omni.voice_creation (0).
2026-09-09Upscaler: POST /upscale-image and POST /upscale-video enhance and enlarge existing media (1× / 2× / 4×; image output up to 8K, video output up to 4K). Pass one of your own generated files or a public HTTPS URL; a video is downloaded and measured before credits are charged (per input second). Images are priced by output tier. Results are polled through the existing /image-status and /video-status endpoints; GET /credits lists the rates under costs.upscale.
2026-09-04Reference videos on /generate-video for seedance-2-direct and seedance-2-5: pass public HTTPS MP4/MOV URLs as reference_video_urls (3 clips / 15 s total on 2.0, 10 clips / 30 s total on 2.5). Every clip is downloaded, measured and normalized before credits are charged, and the job is billed at the with-video per-second rate over reference + output seconds (see the model table; the /credits table lists the rates under with_reference_video). Reference images now work on seedance-2-5 too (up to 30; 2.0 stays at 9). custom accounts need a negotiated reference-video rate first.
2026-09-02Documentation refresh against the live API. All prices on this page are now stated for the api_plus10 tier, with a Pricing tiers section explaining the rounding; pricing_tier was added to GET /me and GET /credits. The GET /credits sample is rendered from the live cost table, and the unused character_creation / character_finalize keys were removed from costs. Documented reference_image_urls / reference_images_base64 on /generate-video (seedance-2-direct, live since 2026-05-31) and reference_image_urls on /generate-image. Example ids use the real kv-<8hex> format, timestamps the real YYYY-MM-DD HH:MM:SS format. Corrected the concurrency table, the rate-limit table (/me, /usage, /transactions), /usage's credits_used field, the Omni character fields (name is required, description optional) and the Omni reference-video cap (90 MB). The Omni character responses no longer include the upstream character id — character_id is the only handle. GET /omni/video/<job_id> documents prompt_fixable; GET /image-status documents model, aspect_ratio, resolution and prompt.
2026-08-27seedance-2-5 now renders 1080p at 111 credits per second standard basis (previously rejected with 400; the provider added the tier). 480p/720p prices are unchanged and 4K remains unavailable. custom accounts need a negotiated rate for this model (otherwise 400). The /credits cost table lists the new tier automatically.
2026-08-20Added the seedance-2-5 video model to /generate-video and the Script Runner. It is a different model from seedance-2-direct, not a variant: 480p/720p only (no 1080p or 4K — those are rejected with 400), durations of 4–30 whole seconds, and 20/45 credits per second. The /credits cost table now also lists the 480p tier, which was previously omitted for every per-second model.
2026-08-16Added idempotent music extension via POST /extend-music: extend a completed partner-owned music variation with JSON, or upload AAC/FLAC/M4A/MP3/OGG/WAV audio through multipart form data. Added operation and persisted partner_job_id to music status responses, plus the owner-scoped GET /music recovery/list endpoint. Music uploads are limited to 95,000,000 bytes and 8 minutes.
2026-08-15Added the standalone Captions resource: start from a completed partner-owned video or direct MP4 upload, resumable chunked upload to 1 GB / 30 min, server-authoritative styles, async status/list, transcript chunks, and free text edit/re-burn. Normal cleanup windows are 14 days for Caption output and 2 days for uploaded edit sources; storage pressure may prune sooner. no_speech and first-run failures refund automatically.
2026-08-14The reserved CHARACTER: scene line (see Script Runner) may name several characters or character ids; the partner endpoint strips the line and ignores the names, for parity with the in-app Script Runner (2026-08-13).
2026-07-27Omni videos accept reference images (image_urls / image_base64) and one reference video (video_url + video_start / video_end) within the shared budget of 7; a reference video adds a duration-dependent surcharge, and custom accounts with negotiated reference-video rates pay those all-in.
2026-06-26Added Omni: characters (POST / GET / DELETE /omni/characters), POST /omni/video with status + list, the Omni Script Runner batch endpoint and POST /omni/improve-prompt. Added Animations: POST /generate-animation (8 s / 1080p / audio, polled via /video-status) and the synchronous POST /merge-videos.
2026-06-09Nano Banana Pro per-image price raised: 2K 14 → 15 credits, 4K 20 → 22 credits (applies to /generate-image). Script Runner per-scene image basis is unchanged (still 14 credits per character generation).
2026-05-31/generate-video accepts reference_image_urls / reference_images_base64 for seedance-2-direct: up to 9 multimodal reference images, a separate mode from the start/end frames (combining both returns 400).
2026-05-20/generate-image and /images now return public_id / public_ids (kv-<8hex> format, same shape as video job_id) alongside the existing integer generation_id. Either ID can be passed to /image-status/<id>. Existing integer-based integrations continue to work unchanged.
2026-05-16/generate-image now accepts a model field (nano-banana-pro default, or gpt-image-2 for ChatGPT 2.0) and a quality field (Nano Banana Pro: 2K/4K — GPT Image 2: 1K/2K/4K). Per-image cost depends on model + quality (14–29 credits).
2026-05-11Added optional partner_job_id field to /analyze, /generate-video, /generate-image, and /script-runner for cross-referencing with your own internal job IDs. Persisted and returned in all GET list responses. Format: 1-64 chars, alphanumeric + -/_. Also added model_used to analysis + image list responses.
2026-05-11Start-frame handling on /generate-video is now per-model: Veo 3.1 (Quality / Fast) now correctly uses the image_url / image_base64 (previously silently dropped for partner-API uploads). kling-v3-motion-control returns 400 when a frame is provided, instead of accepting the request and ignoring/failing later. (Seedance start frames and, since 2026-05-31, reference images are supported — see the per-model table on Generate Video.)
2026-05-09Added voiceover (ElevenLabs TTS) endpoints: POST /generate-voiceover, GET /voiceover/voices, GET /voiceovers
2026-04-25Documentation restructured around the four core products (Ad Analysis, Image Generation, Video Generation, Script Runner). The legacy Characters API (2026-03-23) was removed — generate a character image via Image Generation and pass the resulting image_url to image-to-video models instead. (Character-consistent video returned with Omni on 2026-06-26.)
2026-04-05Added image generation endpoints (generate-image, image-status, images). Added analyses list endpoint. Added pagination (offset/total) to all list endpoints. Added 6 new video models (image-to-video, motion control, Seedance, Veo). Added quality, sound, negative_prompt, image_url parameters to video generation. Removed deprecated Sora 2 models.
2026-03-27Added Kling V3 and Kling O3 models for video generation (Kling O3 was retired on 2026-05-19; Kling V3 remains)
2026-03-23Added character creation and finalization endpoints
2026-03-22Initial v1 release: ad analysis, video generation, credit management

Support

Need an API key? Use the self-serve signup form — your key is generated instantly after email verification.

For integration help, custom pricing, higher rate limits, or bug reports, contact us at [email protected].

Faster for short questions: the Knightvision Discord has an #api-support channel, and support tickets there are private.

We use Google Ads, Meta and Whop conversion tracking to measure whether our ads work. Nothing is stored on your device and no data about you is sent for advertising unless you accept. Privacy