Skip to main content
The varg API is a single REST API for AI media generation across 8 providers (Fal, ElevenLabs, Higgsfield, PiAPI, HeyGen, Together, Groq, Rendi). One API key, ~80 models, async jobs, stable output URLs at s3.varg.ai, and transparent credit billing.

Base URL

https://api.varg.ai/v2

Authentication

All requests (except GET /pricing) require a varg API key:
Authorization: Bearer varg_xxx
Get your API key at app.varg.ai or run bunx vargai login. See Authentication for details.

Core concepts

The API vocabulary has three levels:
ConceptExampleWhat it is
Toolimage, video, speechA capability. Each tool has its own endpoint (POST /v2/image) and unified input schema.
Modelflux_schnell, kling_v3What you put in the model field. One model can be served by multiple providers.
Providerfal, elevenlabsWho actually runs the generation. varg picks the best provider automatically.
Tools: image, video, speech, music, transcription, ffmpeg, render, pipeline. Discover them at runtime via GET /v2/tools.

Quick start

Every generation is an async job: create it, poll until terminal, read the output URL.
# 1. Create a job (202 Accepted)
JOB=$(curl -s -X POST https://api.varg.ai/v2/video \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "kling_v3", "prompt": "cat jumping over a fence", "duration": 5}')

JOB_ID=$(echo $JOB | jq -r '.id')

# 2. Poll until status is terminal
curl -s https://api.varg.ai/v2/jobs/$JOB_ID \
  -H "Authorization: Bearer $VARG_API_KEY" | jq '{status, output}'

# 3. When completed, the video URL is in output.outputs[0].url
# {
#   "status": "completed",
#   "output": {
#     "version": "v1",
#     "outputs": [{
#       "url": "https://s3.varg.ai/files/acc_x/file_abc.mp4",
#       "file_id": "file_abc123",
#       "media_type": "video/mp4"
#     }]
#   }
# }
Creation responses include ready-made lifecycle URLs:
{
  "id": "job_a1b2c3d4e5f6",
  "status": "queued",
  "estimated_cost_cents": 221,
  "urls": {
    "self": "https://api.varg.ai/v2/jobs/job_a1b2c3d4e5f6",
    "status": "https://api.varg.ai/v2/jobs/job_a1b2c3d4e5f6/status",
    "cancel": "https://api.varg.ai/v2/jobs/job_a1b2c3d4e5f6/cancel",
    "retry": "https://api.varg.ai/v2/jobs/job_a1b2c3d4e5f6/retry",
    "refresh": "https://api.varg.ai/v2/jobs/job_a1b2c3d4e5f6/refresh"
  }
}

Job lifecycle

queued → submitting → running → completed | failed | cancelled
StatusMeaning
queuedAccepted, waiting for a provider slot
submittingBeing sent to the provider
runningThe provider is generating
completedDone — read output.outputs[0].url
failedCheck the error field; retry with POST /jobs/{id}/retry
cancelledAborted via POST /jobs/{id}/cancel
Poll GET /v2/jobs/{id} (full view) or GET /v2/jobs/{id}/status (lightweight, includes progress 0..1 and progress_message). A stuck job is guaranteed to reach a terminal status — the platform times out jobs after 30 minutes.
Instead of polling, set options.webhook_url in your request. When the job finishes, varg POSTs the job snapshot to your URL, signed with X-Varg-Signature: v1=<hmac-sha256>, with 8 retries and exponential backoff.

Request format

All generation endpoints take a flat JSON body: model plus tool-specific fields at the top level.
{
  "model": "kling_v3",
  "prompt": "cat jumping over a fence",
  "duration": 5,
  "aspect_ratio": "16:9",
  "files": [{ "url": "https://s3.varg.ai/files/acc_x/cat.jpg" }],
  "provider_options": { "fal": { "seed": 42 } },
  "options": { "webhook_url": "https://example.com/hook" }
}
FieldApplies toDescription
modelallModel id — required
promptimage, video, musicText prompt (max 2000 chars)
textspeechText to speak (max 10000 chars)
voicespeechVoice name
audio_urltranscriptionPublic URL of the audio file
durationvideo, musicSeconds
aspect_ratioimage, videoe.g. "16:9", "9:16"
filesimage, videoInput files by URL (max 10) — start frames, edit sources, lipsync inputs
provider_optionsallProvider-native overrides, keyed by provider
options.webhook_urlallCompletion webhook
Media inputs are always passed by URL — upload local files first via POST /v2/files to get a stable varg URL.

Model resolution

Most requests should just use the model name. Pass a canonical varg name (seedance_2, kling_v3, flux_schnell) and varg auto-routes by priority and input shape — if your request includes files, routes that map files are preferred; the first route whose schema accepts your input wins.
{ "model": "seedance_2" }   // canonical name — varg picks the route (recommended)
For advanced control, two more address forms exist:
{ "model": "fal:seedance_2" }                                      // provider prefix — routes filtered to fal
{ "model": "fal:bytedance/seedance-2.0/fast/reference-to-video" }  // exact model_key — pins one route
  • Provider prefix — same auto-routing, restricted to routes on that provider.
  • Exact model_key — pins one specific route. model_key values are a permanent public contract and safe to hardcode.
Every route’s model_key is listed in GET /v2/models. Two guarantees:
  1. No silent substitution. varg only picks which route serves the model you named — never a different model family.
  2. Transparent routing. The job records which provider model actually ran (provider, provider_model fields), and POST /v2/estimate returns the resolved_model_key before you submit.
Within a model, input shape can select a variant automatically: sending an image in files to kling_v3 routes to its image-to-video endpoint. Same family, same pricing rules. Model ids use underscores (kling_v3, flux_schnell). Dashed spellings (kling-v3) are accepted and normalized automatically. Browse all models and prices at GET /v2/pricing (public, no auth).

Model catalog

GET /v2/models (public, no auth) returns the full catalog: every model with its routes, pricing, and operational hints. Filter by tool with ?tool=video.
curl -s "https://api.varg.ai/v2/models?tool=video"
{
  "version": "v1",
  "data": [
    {
      "model": "seedance_2",
      "tool": "video",
      "routes": [
        {
          "model_key": "fal:bytedance/seedance-2.0/text-to-video",
          "provider": "fal",
          "modality": ["text-to-video"],
          "output_type": "video",
          "description": "Seedance 2.0 standard tier, up to 4k",
          "priority": 1,
          "accepts_files": false,
          "pricing": {
            "pricing_id": "ppr_...",
            "rule": { "type": "per_second_conditional", "param": "duration", "rates": ["..."] },
            "user_price_estimate_cents": 479
          },
          "ops": {
            "delivery": "webhook",
            "poll_interval_ms": 5000,
            "estimated_duration_ms": 180000,
            "max_timeout_ms": 1800000
          },
          "schema_url": "/v2/tools/video?model=fal:bytedance/seedance-2.0/text-to-video"
        }
      ]
    }
  ]
}
Each route’s model_key can be used directly in the model field to pin that route.

Discovering schemas at runtime

Agents and dynamic clients can discover the full API without reading docs:
# List all tools
curl -s https://api.varg.ai/v2/tools -H "Authorization: Bearer $VARG_API_KEY"

# Get the image tool's input schema, output shape, and a worked example
curl -s https://api.varg.ai/v2/tools/image -H "Authorization: Bearer $VARG_API_KEY"

# Get the explicit per-provider options for a specific model
curl -s "https://api.varg.ai/v2/tools/image?model=flux_schnell" \
  -H "Authorization: Bearer $VARG_API_KEY"

# Call any tool generically (same as POST /v2/image)
curl -s -X POST https://api.varg.ai/v2/tools/image/call \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "flux_schnell", "prompt": "a cat astronaut"}'

Pricing and billing

1 credit = 1 cent = $0.01. The billing flow:
  1. Reserve — at job creation, the estimated cost is held from your balance (402 if insufficient).
  2. Commit — on completion, the hold is charged and a usage record written.
  3. Release — on failure, the hold is returned (most models; some bill partial work).
# Price a request WITHOUT creating a job
curl -s -X POST https://api.varg.ai/v2/estimate \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "seedance_2", "prompt": "cat", "duration": 5}'
# {"valid": true, "model": "seedance_2", "tool": "video",
#  "resolved_model_key": "fal:bytedance/seedance-2.0/text-to-video",
#  "pricing": {"provider_cost_cents": 152, "user_price_cents": 160,
#              "markup_percent": 5, "pricing_id": "ppr_..."}}

# Check your balance
curl -s https://api.varg.ai/v2/billing/balance \
  -H "Authorization: Bearer $VARG_API_KEY"
# {"available": 8200, "reserved": 300, "total_balance": 8500, ...}
Each job carries its pricing in the response: pricing.estimated, pricing.actual, pricing.billed_units (e.g. {"seconds": 6}), and a pricing_id that pins the price rule the job was charged under — mid-flight price changes never affect running jobs.

Caching

Results are cached by canonical input. Repeating an identical request returns a completed job instantly with actual_cost_cents: 0 and pricing.cached: truecache hits are free.

Idempotency

Pass an Idempotency-Key header on any job-creating POST to make retries safe:
curl -s -X POST https://api.varg.ai/v2/image \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Idempotency-Key: my-unique-key-1" \
  -H "Content-Type: application/json" \
  -d '{"model": "flux_schnell", "prompt": "a cat astronaut"}'
The first request creates the job (202). Retrying with the same key returns the same job (200) — no duplicate charge, no duplicate generation.

Files and lineage

Outputs are mirrored to varg storage and served from stable s3.varg.ai URLs. Every output gets a file record with an AI-generated title, a thumbnail, and lineage back to the job that created it:
# Upload an input file (raw binary body, max 200MB)
curl -s -X POST https://api.varg.ai/v2/files \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Content-Type: image/jpeg" \
  -H "X-Filename: cat.jpg" \
  --data-binary @cat.jpg
# {"file_id": "file_abc123", "url": "https://s3.varg.ai/files/...", ...}

# Which job made this file? (get the recipe: prompt, model, cost)
curl -s -X POST https://api.varg.ai/v2/lineage \
  -H "Authorization: Bearer $VARG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file_id": "file_abc123"}'
Send X-Content-Hash: sha256:<hex> on upload for content deduplication, or pre-check with GET /v2/files/check?hash=.

Error format

All errors use one envelope:
{
  "error": {
    "code": "model_not_found",
    "message": "Unknown model: flux-shnell",
    "details": null
  }
}
StatusCodeDescription
400invalid_request, invalid_jsonMalformed body
401unauthorizedMissing or invalid API key
402insufficient_balanceBalance too low to reserve the estimated cost
404model_not_found, tool_not_found, job_not_found, file_not_foundUnknown resource
409Job is already terminal (cancel/refresh)
413file_too_largeUpload over 200 MB
422invalid_requestBody failed schema validation (details included)
429rate_limitedToo many requests — honor Retry-After
503no_pricingModel temporarily has no active pricing
model_not_found errors include suggestions in details: did_you_mean lists close model names, and models_url points to /v2/models for the full catalog.

Rate limits

Rate limiting is a sliding window per API key. Every job-creation response carries:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 2026-07-01T10:01:00.000Z
On 429, wait for Retry-After seconds and retry (ideally with the same Idempotency-Key).

Migrating from v1

The v1 gateway (api.varg.ai/v1) is frozen. Key differences in v2:
v1v2
job_id fieldid field
output.url (single)output.outputs[] (array, each with file_id)
GET /jobs/{id}/stream (SSE)Removed — poll or use options.webhook_url
DELETE /jobs/{id}POST /jobs/{id}/cancel
GET /balanceGET /billing/balance (richer breakdown)
GET /usageGET /billing/usage
POST /ffmpeg/trim etc.Single POST /ffmpeg, operation selected by model
POST /ffmpeg/probePOST /files/probe
GET /voicesNot yet ported — pass voice by name
Model names with dashesUnderscores canonical (dashes still accepted)
Flat error shape{"error": {"code", "message"}} envelope