VanceAI Open API documentation
VanceAI API
Introduction
Run VanceAI's image and video AI processing directly from your own application. One API key, one unified job API for both media types, and clean tool names that hide all the storage, upload and scheduling details.
What you can do
The API exposes VanceAI's processing engine through a small, stable set of REST endpoints. You submit a job with a file or a URL, poll for completion, and download the result. Both image and video tools live under the same /v1/jobs resource: the tool you pick determines the media type and the operation.
| Media | Example tools |
|---|---|
| Image | Upscale, sharpen, denoise, background removal, restore, cartoonize, passport photo |
| Video | Upscale, SDR→HDR, face enhancement |
Base URL
All API requests are made to:
https://cloud-vanceai.vanceai.com/api/v1Every response is application/json. All requests must be authenticated with an API key. See Authentication.
How it works
The API is fully asynchronous and built around three steps:
POST /v1/jobs → returns a job_id immediately (status: "queued")
GET /v1/jobs/{id} → poll until status is "succeeded" or "failed"
GET /v1/jobs/{id}/result → fetch a fresh, never-expiring download linkA POST never blocks while uploading or processing. It validates your request, reserves the job, and returns a job_id in milliseconds. A background worker then handles the upload, processing, and result, so even multi-gigabyte videos work without hitting HTTP timeouts.
Core concepts
| Concept | Summary |
|---|---|
| Jobs & lifecycle | A unit of processing. Created with POST, polled with GET, has a lifecycle: queued → processing → succeeded / failed / canceled. |
| Tools | Clean, stable names (e.g. upscale, video_upscale) that select the media type and operation. |
| Credits & billing | Processing consumes credits. They are reserved (frozen) when a job starts and refunded in full if it fails or is canceled. |
| Managing API keys | sk_live_ keys identify your account. Created and revoked from your account dashboard. |
Polling only in v1
GET /v1/jobs/{id} for completion. Webhook callbacks are planned for v1.1. See the changelog for the full v1 scope.Next steps
- Authentication: get and use your API key.
- Quickstart: your first job in a few minutes.
- Jobs & lifecycle: the full endpoint reference.
Getting started
Authentication
The VanceAI API authenticates requests with an API key. Your key carries the privileges of your account, so keep it secret.
API keys
Keys look like this and always start with the sk_live_ prefix:
sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6Create and revoke keys from your VanceAI account dashboard. The full key is shown only once, at creation time. Store it somewhere safe. If you lose it, revoke the key and create a new one. See Managing API keys.
Authenticating requests
Send your key in the Authorization header as a bearer token on every request:
Authorization: Bearer sk_live_a1b2c3d4e5f6...For example, to check your credit balance:
curl https://cloud-vanceai.vanceai.com/api/v1/credits \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..."Keep your key secret
- Never expose
sk_live_keys in client-side code, mobile apps, or public repositories. - Make API calls only from your own backend.
- Use separate keys per application or environment so you can revoke one without affecting the others.
Authentication errors
A missing, malformed, or revoked key returns 401 with a stable error code:
{
"error": {
"code": "invalid_api_key",
"message": "The API key provided is invalid or has been disabled.",
"type": "auth"
}
}See Errors for the full list of codes and how to handle them.
Getting started
Quickstart
This guide walks through a complete image-upscaling job: submit, poll, download. It takes a few minutes and uses only curl.
1. Submit a job
Send the image as multipart form data, along with the tool and its config. You can upload a file directly (up to 200 MB) or pass a public image_url instead.
curl https://cloud-vanceai.vanceai.com/api/v1/jobs \
-H "Authorization: Bearer sk_live_..." \
-F [email protected] \
-F tool=upscale \
-F 'config={"scale":4,"face_enhance":true}' \
-F output_format=jpgOr submit a URL with a JSON body:
curl https://cloud-vanceai.vanceai.com/api/v1/jobs \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/photo.jpg",
"tool": "upscale",
"config": { "scale": 4, "face_enhance": true },
"output_format": "jpg"
}'The response returns immediately with a job_id and a queued status:
{
"job_id": "job_3f2a9c...",
"status": "queued",
"tool": "upscale",
"media": "image",
"credits_estimated": 1.0,
"credits_charged": 0,
"created_at": "2026-06-11T08:30:00Z"
}2. Poll until it finishes
Poll GET /v1/jobs/{job_id} until status becomes succeeded (or failed). For images, polling every ~2 seconds is plenty.
curl https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c... \
-H "Authorization: Bearer sk_live_..."{
"job_id": "job_3f2a9c...",
"status": "succeeded",
"progress": 100,
"tool": "upscale",
"result_url": "https://.../preview?sign=...",
"result": { "width": 4000, "height": 3000, "size": 2451234 },
"error": null,
"created_at": "2026-06-11T08:30:00Z",
"finished_at": "2026-06-11T08:30:42Z"
}3. Download the result
The result_url from step 2 is a signed link that eventually expires. For a link that never expires, call the result endpoint, which re-signs on demand:
curl -L https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c.../result \
-H "Authorization: Bearer sk_live_..." \
-o upscaled.jpgThat's the whole loop
tool name and config change. Video adds one step up front: upload the file with the two-phase upload and pass its upload_id instead of an image. Browse the full list on the Tools page.Guides
Jobs & lifecycle
A job represents one processing task, image or video. You create it, poll its status, and download the result. This page is the full reference for the /v1/jobs resource.
The job lifecycle
Every job moves through these statuses:
| status | Meaning |
|---|---|
queued | Accepted and waiting for a worker. May sit here briefly depending on backlog. |
processing | Being worked on. The phase field details the sub-step. |
succeeded | Done. result_url and result are available. |
failed | Failed. error contains a stable code. |
canceled | Canceled by you. Reserved credits are refunded. |
While processing, the phase field narrows it down: uploading → syncing → processing. The numeric progress (0–100) is only meaningful once phase is processing; during upload/sync it stays 0. For large videos the upload phase can take minutes, so judge "is it stuck?" from phase, not progress.
Upload a video
Videos are uploaded directly to storage in two steps, so the bytes never pass through the API and multi-gigabyte files aren't limited by request size. First call POST /v1/uploads to get a one-time presigned upload URL, then PUT the file to it. Images skip this — upload them inline when you create the job (see below). Direct video multipart upload is not supported; source_url is still accepted as an alternative.
/v1/uploadsRequest parameters
| Field | Type | Description |
|---|---|---|
| file_namerequired | string | The file name, including a video extension (e.g. clip.mp4). Used to derive the storage key and content type. |
| file_size | number | Optional. File size in bytes. When provided, the request fails fast if it exceeds the single-upload limit (max_bytes in the response, ~4.9 GB); the limit is enforced at upload time regardless. |
| content_type | string | Optional MIME type (e.g. video/mp4). Inferred from the extension when omitted. |
Example
curl https://cloud-vanceai.vanceai.com/api/v1/uploads \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"file_name": "clip.mp4",
"file_size": 734003200,
"content_type": "video/mp4"
}'Response — 201 Created
{
"upload_id": "upl_8f3b1c2a...",
"put_url": "https://<bucket>.r2.cloudflarestorage.com/...&X-Amz-Signature=...",
"expires_at": "2026-06-13T08:30:00Z",
"max_bytes": 5261334937
}put_url is a presigned URL valid for 24 hours. Upload the raw bytes to it with a single PUT — no auth header, just the file:
curl -X PUT --upload-file clip.mp4 \
-H "Content-Type: video/mp4" \
"https://<bucket>.r2.cloudflarestorage.com/...&X-Amz-Signature=..."Once the PUT succeeds, create the job by passing the upload_id instead of a file — see Create a job. Each upload_id is single-use.
curl https://cloud-vanceai.vanceai.com/api/v1/jobs \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"upload_id": "upl_8f3b1c2a...",
"tool": "video_upscale",
"config": { "scale": 2, "codec": "h264" }
}'{
"job_id": "job_3f2a9c...",
"status": "queued",
"tool": "video_upscale",
"media": "video",
"credits_estimated": 0,
"credits_charged": 0,
"created_at": "2026-06-11T08:30:00Z"
}Size limits & expiry
PUT can be up to ~4.9 GB (the exact byte cap is max_bytes). Requesting an upload for a larger file returns 413 file_too_large. The presigned URL expires after 24 hours; an unused upload_id then becomes upload_expired and you must request a new one.Create a job
/v1/jobsHow you provide the media depends on the type. Images: a direct file upload (multipart) or a public URL. Videos: an upload_id from the upload step above, or a public source_url. The tool selects the media type and operation.
Request parameters
| Field | Type | Description |
|---|---|---|
| image / file | file | Image only: binary upload (multipart). file is an accepted alias. For video, use upload_id. |
| upload_id | string | Video only: the upload_id returned by POST /v1/uploads after you upload the file. Single-use. |
| image_url / source_url | string | A publicly reachable URL to fetch (image or video). Use instead of a file / upload_id. source_url is an accepted alias. |
| toolrequired | string | The operation, e.g. upscale or video_upscale. See Tools. |
| config | object | Tool-specific options (image and video options differ). See Tools. |
| output_format | string | Image only: jpg (default), png, or webp. Ignored for video (use config.codec). |
Idempotency
Pass an Idempotency-Key header to safely retry a create request without duplicating the job. See Rate limits & idempotency.
Example
curl https://cloud-vanceai.vanceai.com/api/v1/jobs \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: a-unique-string" \
-F [email protected] \
-F tool=upscale \
-F 'config={"scale":4,"face_enhance":true}'Response: 202 Accepted
{
"job_id": "job_3f2a9c...",
"status": "queued",
"tool": "upscale",
"media": "image",
"credits_estimated": 1.0,
"credits_charged": 0,
"created_at": "2026-06-11T08:30:00Z"
}The response returns before any credits are charged (credits_charged is 0). credits_estimated is a pre-flight estimate; the authoritative charge appears once the job reaches processing. See Credits & billing.
Large files & URLs
413 file_too_large — switch to source_url. Videos never go through multipart — upload them via the upload step (single PUT up to ~4.9 GB) or pass a source_url. Server-side URL fetching is protected against SSRF: only http/https are allowed and private / internal network targets are rejected.Retrieve a job
/v1/jobs/{job_id}Returns the current status of a job. Poll this until the job reaches a terminal status.
Response: 200 (processing)
{
"job_id": "job_3f2a9c...",
"status": "processing",
"phase": "uploading",
"progress": 0,
"tool": "video_upscale",
"media": "video",
"result_url": null,
"result": null,
"error": null,
"created_at": "2026-06-11T08:30:00Z",
"finished_at": null
}Response: 200 (succeeded)
{
"job_id": "job_3f2a9c...",
"status": "succeeded",
"progress": 100,
"tool": "upscale",
"result_url": "https://.../preview?sign=...",
"result": { "width": 4000, "height": 3000, "size": 2451234 },
"error": null,
"created_at": "2026-06-11T08:30:00Z",
"finished_at": "2026-06-11T08:30:42Z"
}The result shape is { width, height, size } for images and { width, height, duration, size, codec } for video. The result_url is a signed link that will expire. For a permanent link, use the result endpoint below.
Get the result
/v1/jobs/{job_id}/resultRedirects (302) to a freshly signed result URL, re-signed on every call, so the link never goes stale. Use this whenever you need to download the output, rather than caching result_url.
curl -L https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c.../result \
-H "Authorization: Bearer sk_live_..." \
-o result.jpgResult not ready yet
202 result_not_ready. Just retry shortly. Downloading the result never costs extra credits.Cancel a job
/v1/jobs/{job_id}/cancelCancels a job that is still queued or processing. Mainly useful for long-running video jobs. Any reserved credits are refunded in full.
{ "job_id": "job_3f2a9c...", "status": "canceled" }A job that has already succeeded, failed, or been canceled returns 409 not_cancelable.
Delete a job
/v1/jobs/{job_id}Deletes a job record and its associated result: useful for cleaning up processed assets for privacy or compliance. If the job is still in progress, it is canceled (and credits refunded) first.
{ "job_id": "job_3f2a9c...", "deleted": true }- Deleting an already-deleted job still returns
200 { "deleted": true }(idempotent). - You can only delete jobs that belong to your own account; others return
404 job_not_found.
Guides
Tools
The tool you pass to POST /v1/jobs selects both the media type and the operation. Image tools use plain names; video tools are prefixed with video_.
Image tools
Image tools take a config object. The face_enhance option (boolean) is optional on most tools. Set output_format on the request to choose jpg / png / webp.
| tool | config options |
|---|---|
upscale | scale (2 / 4 / 8, default 2), face_enhance |
sharpen | face_enhance |
denoise | face_enhance |
remove_bg | none |
restore | face_enhance |
cartoonize | style, face_enhance |
passport_photo | none |
custom | prompt, face_enhance |
{ "scale": 4, "face_enhance": true }Validation
upscale.scale must be 2, 4, or 8; custom requires a non-empty prompt. Invalid values return 400 invalid_parameter. You don't need to send the source dimensions. They're detected on upload and used to compute the target size.Video tools
Video tools are processed end to end (no preview-clip mode in v1). The quality (high / medium / low) and codec (h264 / h265) options apply to video_hdr and video_face_enhance only. For video_upscale the output encoding is determined by the upscaler engine, so quality / codec are not used (sending them has no effect).
| tool | config options |
|---|---|
video_upscale | scale (2 / 4, default 2), fps |
video_hdr | quality, codec, hdr_color_gamut |
video_face_enhance | face_enhanced_mode, quality, codec |
{ "scale": 2 }Source dimensions are measured server-side
ffprobe) after upload — it does not trust client-reported values. You may optionally include source_width / source_height / duration in config to get a pre-flight credit estimate, but the authoritative charge is always based on the measured values. Output resolution is capped (long side 4096, short side 2160); exceeding it fails the job with resolution_limit_exceeded before any credits are charged.Unsupported tools
Requesting a tool that doesn't exist, or one that isn't available yet (the video translate, colorize, and subtitle operations are not in v1), returns 400 unsupported_tool.
Guides
Credits & billing
Processing consumes credits from your account. Credits are reserved when a job starts and either confirmed on success or refunded in full on failure or cancellation.
How billing works
The credit flow over a job's lifetime:
| Stage | What happens to credits |
|---|---|
Job created (queued) | Nothing charged yet. credits_charged is 0. |
| Worker starts processing | Credits are reserved (frozen). If your balance is too low, the job fails with insufficient_credits. |
Job succeeded | The reservation is confirmed: credits are deducted. |
Job failed / canceled | The reservation is released: credits are refunded in full. |
| Downloading the result | Free. Never charged again. |
Pre-flight check on submit
POST a job, the API does a quick balance check before queuing it. If the estimated cost already exceeds your available balance, you get 402 insufficient_credits immediately, so a large upload isn't wasted. The authoritative reservation still happens when the worker starts.Get your balance
/v1/creditscurl https://cloud-vanceai.vanceai.com/api/v1/credits \
-H "Authorization: Bearer sk_live_..."{ "balance": 999.8, "used": 234.2, "frozen": 12.5 }| Field | Type | Description |
|---|---|---|
| balance | number | Credits available to spend right now (already excludes anything frozen). |
| used | number | Credits consumed by completed jobs. |
| frozen | number | Credits currently reserved by in-progress jobs (not yet confirmed or released). |
Credit amounts are decimals (some operations cost a fraction of a credit).
Guides
Rate limits & idempotency
Two limits protect the platform: a per-key request rate, and a per-account cap on concurrent jobs. Idempotency keys let you retry job creation safely.
Request rate (per key)
Each API key has a request-rate limit. Exceeding it returns 429 rate_limited with headers describing your current window:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718093460Responses may also include a Retry-After header (seconds); respect it before retrying.
Concurrency (per account)
Because a single video job can run for many minutes, request-rate alone isn't enough. There is also a cap on the number of jobs your account can have in flight (queued or processing) at once, typically lower for video than for images.
Submitting beyond the cap returns 429 concurrency_limit_exceeded. Wait for some in-flight jobs to finish, then retry. The count is aggregated per account, so spreading work across multiple keys does not bypass it.
Polling guidance
v1 has no webhooks, so you poll GET /v1/jobs/{id}. To avoid hammering the API:
- Suggested interval: about 2s for images, 5–10s for video.
- Respect the
Retry-Afterheader when present; it's a hint for the next poll. - Stop polling once the job reaches a terminal status (
succeeded/failed/canceled).
Idempotency
Network blips can leave you unsure whether a POST /v1/jobs succeeded. To retry safely, send a unique Idempotency-Key header:
curl https://cloud-vanceai.vanceai.com/api/v1/jobs \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 8f3b1c2a-...-unique" \
-F [email protected] \
-F tool=upscale- Replaying the same key returns the original job: no duplicate job, no double charge.
- Generate one fresh key per logical request (a UUID works well).
- Reusing a key with a different
toolorconfigreturns409 idempotency_conflict.
How long a key is valid
Guides
Errors
Errors come in a consistent JSON shape with a stable code you can branch on. There are two kinds: request errors (returned with an HTTP error status) and job errors (returned on a succeeded HTTP poll with status: failed).
Error format
{
"error": {
"code": "insufficient_credits",
"message": "Your account does not have enough credits for this operation.",
"type": "billing"
}
}code is a stable string, so branch on it. message is human-readable and may change. type is the broad category: auth, billing, validation, processing, rate_limit, or server.
Request errors (HTTP status)
When a request is rejected outright, you get the matching HTTP status and an error body:
| HTTP | code | When |
|---|---|---|
| 401 | invalid_api_key | API key is missing, invalid, or disabled. |
| 401 | unauthorized | Authentication failed. |
| 402 | insufficient_credits | Pre-flight balance check failed at submit. |
| 400 | invalid_parameter | A parameter is missing or invalid. |
| 400 | unsupported_tool | The tool does not exist or is not available in v1. |
| 403 | geo_restricted | Blocked in your region. |
| 404 | job_not_found | No such job under your account. |
| 413 | file_too_large | Image direct upload over 200 MB, or a video upload over the single-PUT limit (~4.9 GB). |
| 415 | unsupported_format | Direct upload is an unsupported format. |
| 409 | not_cancelable | Job is already in a terminal state. |
| 409 | idempotency_conflict | Same idempotency key used with a different tool/config. |
| 404 | upload_not_found | The upload_id does not exist or belongs to another account. |
| 409 | upload_already_consumed | The upload_id was already used by another job (single-use). |
| 410 | upload_expired | The upload_id expired (24h) before being used — request a new one. |
| 202 | result_not_ready | Result still syncing; retry /result shortly. |
| 429 | rate_limited | Per-key request rate exceeded. |
| 429 | concurrency_limit_exceeded | Too many in-flight jobs for the account. |
| 503 | service_unavailable | A downstream dependency is temporarily unavailable; retry. |
| 500 | internal_error | Unexpected server error. |
Job errors (status: failed)
When a job fails *asynchronously*, polling GET /v1/jobs/{id} returns HTTP 200 with status: "failed" and a code in error.code. Don't expect a 5xx for a failed job: the query itself succeeded.
| `error.code` | type | When |
|---|---|---|
processing_failed | processing | The processing engine could not complete the task. |
processing_timeout | processing | The job exceeded the maximum processing time (credits refunded). |
file_too_large | validation | A fetched source_url exceeded 200 MB. |
unsupported_format | validation | A fetched source_url had a type that doesn't match the tool. |
ssrf_blocked | validation | The source_url resolved to a disallowed (private/internal) address. |
insufficient_credits | billing | Balance was consumed before the authoritative reservation. |
upload_incomplete | validation | The uploaded object is missing or incomplete — the file wasn't fully PUT before the job was created. |
resolution_limit_exceeded | validation | Output resolution exceeds the engine limit (long side 4096, short side 2160). Not charged. |
Retrying
service_unavailable and transient 5xx responses as retryable (ideally with backoff). Treat billing, auth and validation errors as permanent: fix the request rather than retrying.Account
Managing API keys
API keys are created and managed from your VanceAI account dashboard. Each key belongs to your account and draws from your account's credit balance.
Creating a key
In your dashboard, create a new key and give it a recognizable name (for example my-app-prod). The full secret (starting with sk_live_) is shown only once, right after creation.
Copy your key immediately
Viewing keys
The dashboard lists your keys by name, status, a short prefix (e.g. sk_live_a1b2) for identification, and the time each key was last used. The full secret is never displayed again, only the prefix.
Revoking a key
Disabling a key takes effect immediately: any further request with it returns 401 invalid_api_key.
- Revocation is permanent. There is no re-enable; create a new key instead.
- You can only manage keys that belong to your own account.
Rotating keys safely
Multiple active keys can coexist, which makes rotation seamless: create a new key, switch your application to it, confirm traffic has moved over, then revoke the old key. Using a distinct key per application or environment limits the blast radius if one is ever leaked.
Resources
Versioning & changelog
The API is versioned in the URL path. The current version is v1, served under /api/v1.
v1 (current)
The first stable version includes:
- Unified jobs API for both image and video:
POST /v1/jobs,GET /v1/jobs/{id},GET /v1/jobs/{id}/result,POST /v1/jobs/{id}/cancel,DELETE /v1/jobs/{id}, andGET /v1/credits. - 8 image tools and 3 video tools (upscale, HDR, face enhancement).
- Large files: image direct uploads up to 200 MB; videos via two-phase direct upload; any media via
source_url(SSRF-protected). - Asynchronous processing with a pre-flight credit check and a background worker.
- Credit reservations with full refund on failure or cancellation.
- Stable error codes,
Idempotency-Keysupport, per-key rate limits and per-account concurrency limits. - Video two-phase direct upload —
POST /v1/uploadsissues a presigned URL so videos upload straight to storage (singlePUTup to ~4.9 GB), bypassing the request-size limit. - Accurate video billing — the server measures resolution, duration and frame rate with
ffprobe, and pre-checks output resolution against the engine limit.