Skip to content

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.

MediaExample tools
ImageUpscale, sharpen, denoise, background removal, restore, cartoonize, passport photo
VideoUpscale, SDR→HDR, face enhancement

Base URL

All API requests are made to:

text
https://cloud-vanceai.vanceai.com/api/v1

Every 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:

text
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 link

A 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

ConceptSummary
Jobs & lifecycleA unit of processing. Created with POST, polled with GET, has a lifecycle: queued → processing → succeeded / failed / canceled.
ToolsClean, stable names (e.g. upscale, video_upscale) that select the media type and operation.
Credits & billingProcessing consumes credits. They are reserved (frozen) when a job starts and refunded in full if it fails or is canceled.
Managing API keyssk_live_ keys identify your account. Created and revoked from your account dashboard.

Polling only in v1

There are no webhooks in v1. You poll GET /v1/jobs/{id} for completion. Webhook callbacks are planned for v1.1. See the changelog for the full v1 scope.

Next steps

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:

text
sk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Create 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:

text
Authorization: Bearer sk_live_a1b2c3d4e5f6...

For example, to check your credit balance:

bash
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:

json
{
  "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.

Upload a file
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=jpg

Or submit a URL with a JSON body:

Submit a URL
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:

json
{
  "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.

bash
curl https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c... \
  -H "Authorization: Bearer sk_live_..."
json
{
  "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:

bash
curl -L https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c.../result \
  -H "Authorization: Bearer sk_live_..." \
  -o upscaled.jpg

That's the whole loop

Submit → poll → download is the same for every tool — only the 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:

statusMeaning
queuedAccepted and waiting for a worker. May sit here briefly depending on backlog.
processingBeing worked on. The phase field details the sub-step.
succeededDone. result_url and result are available.
failedFailed. error contains a stable code.
canceledCanceled by you. Reserved credits are refunded.

While processing, the phase field narrows it down: uploadingsyncingprocessing. 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.

POST/v1/uploads

Request parameters

FieldTypeDescription
file_namerequiredstringThe file name, including a video extension (e.g. clip.mp4). Used to derive the storage key and content type.
file_sizenumberOptional. 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_typestringOptional MIME type (e.g. video/mp4). Inferred from the extension when omitted.

Example

bash
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

json
{
  "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:

Upload 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.

bash
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" }
  }'
Response: 202 Accepted
{
  "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

A single 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

POST/v1/jobs

How 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

FieldTypeDescription
image / filefileImage only: binary upload (multipart). file is an accepted alias. For video, use upload_id.
upload_idstringVideo only: the upload_id returned by POST /v1/uploads after you upload the file. Single-use.
image_url / source_urlstringA publicly reachable URL to fetch (image or video). Use instead of a file / upload_id. source_url is an accepted alias.
toolrequiredstringThe operation, e.g. upscale or video_upscale. See Tools.
configobjectTool-specific options (image and video options differ). See Tools.
output_formatstringImage 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

bash
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

json
{
  "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

Direct image uploads are capped at 200 MB; larger images return 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

GET/v1/jobs/{job_id}

Returns the current status of a job. Poll this until the job reaches a terminal status.

Response: 200 (processing)

json
{
  "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)

json
{
  "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

GET/v1/jobs/{job_id}/result

Redirects (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.

bash
curl -L https://cloud-vanceai.vanceai.com/api/v1/jobs/job_3f2a9c.../result \
  -H "Authorization: Bearer sk_live_..." \
  -o result.jpg

Result not ready yet

Right after a job succeeds, the result object occasionally needs a few moments to sync. The endpoint retries internally, but for very large videos it may briefly return 202 result_not_ready. Just retry shortly. Downloading the result never costs extra credits.

Cancel a job

POST/v1/jobs/{job_id}/cancel

Cancels a job that is still queued or processing. Mainly useful for long-running video jobs. Any reserved credits are refunded in full.

Response: 200
{ "job_id": "job_3f2a9c...", "status": "canceled" }

A job that has already succeeded, failed, or been canceled returns 409 not_cancelable.

Delete a job

DELETE/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.

Response: 200
{ "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.

toolconfig options
upscalescale (2 / 4 / 8, default 2), face_enhance
sharpenface_enhance
denoiseface_enhance
remove_bgnone
restoreface_enhance
cartoonizestyle, face_enhance
passport_photonone
customprompt, face_enhance
Example config: upscale
{ "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).

toolconfig options
video_upscalescale (2 / 4, default 2), fps
video_hdrquality, codec, hdr_color_gamut
video_face_enhanceface_enhanced_mode, quality, codec
Example config: video_upscale
{ "scale": 2 }

Source dimensions are measured server-side

For accurate billing, the server measures the real width, height, duration and frame rate of your video (via 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:

StageWhat happens to credits
Job created (queued)Nothing charged yet. credits_charged is 0.
Worker starts processingCredits are reserved (frozen). If your balance is too low, the job fails with insufficient_credits.
Job succeededThe reservation is confirmed: credits are deducted.
Job failed / canceledThe reservation is released: credits are refunded in full.
Downloading the resultFree. Never charged again.

Pre-flight check on submit

When you 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

GET/v1/credits
bash
curl https://cloud-vanceai.vanceai.com/api/v1/credits \
  -H "Authorization: Bearer sk_live_..."
Response: 200
{ "balance": 999.8, "used": 234.2, "frozen": 12.5 }
FieldTypeDescription
balancenumberCredits available to spend right now (already excludes anything frozen).
usednumberCredits consumed by completed jobs.
frozennumberCredits 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:

text
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718093460

Responses 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-After header 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:

bash
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 tool or config returns 409 idempotency_conflict.

How long a key is valid

A key stays bound to its job as long as that job exists. Deleting the job releases the key. There is no separate expiry window.

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

json
{
  "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:

HTTPcodeWhen
401invalid_api_keyAPI key is missing, invalid, or disabled.
401unauthorizedAuthentication failed.
402insufficient_creditsPre-flight balance check failed at submit.
400invalid_parameterA parameter is missing or invalid.
400unsupported_toolThe tool does not exist or is not available in v1.
403geo_restrictedBlocked in your region.
404job_not_foundNo such job under your account.
413file_too_largeImage direct upload over 200 MB, or a video upload over the single-PUT limit (~4.9 GB).
415unsupported_formatDirect upload is an unsupported format.
409not_cancelableJob is already in a terminal state.
409idempotency_conflictSame idempotency key used with a different tool/config.
404upload_not_foundThe upload_id does not exist or belongs to another account.
409upload_already_consumedThe upload_id was already used by another job (single-use).
410upload_expiredThe upload_id expired (24h) before being used — request a new one.
202result_not_readyResult still syncing; retry /result shortly.
429rate_limitedPer-key request rate exceeded.
429concurrency_limit_exceededToo many in-flight jobs for the account.
503service_unavailableA downstream dependency is temporarily unavailable; retry.
500internal_errorUnexpected 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`typeWhen
processing_failedprocessingThe processing engine could not complete the task.
processing_timeoutprocessingThe job exceeded the maximum processing time (credits refunded).
file_too_largevalidationA fetched source_url exceeded 200 MB.
unsupported_formatvalidationA fetched source_url had a type that doesn't match the tool.
ssrf_blockedvalidationThe source_url resolved to a disallowed (private/internal) address.
insufficient_creditsbillingBalance was consumed before the authoritative reservation.
upload_incompletevalidationThe uploaded object is missing or incomplete — the file wasn't fully PUT before the job was created.
resolution_limit_exceededvalidationOutput resolution exceeds the engine limit (long side 4096, short side 2160). Not charged.

Retrying

Treat 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

We store only a hash of your key, never the plaintext. Once you close the creation dialog, the full key cannot be retrieved again. If you lose it, revoke the key and create a new one.

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}, and GET /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-Key support, per-key rate limits and per-account concurrency limits.
  • Video two-phase direct uploadPOST /v1/uploads issues a presigned URL so videos upload straight to storage (single PUT up 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.