Durango API

Programmatic access to every chat, image-generation, and video-generation model in Durango. Requests authenticate with a personal API key and consume the same usage credits as the app — there is no separate billing.

Durango API documentation

Base URL
https://durango.sh
Machine-readable
OpenAPI 3.1 spec · no auth required
llms.txt · this reference as plain markdown

All endpoints accept and return JSON, except image/video downloads (raw bytes) and streaming chat (Server-Sent Events). CORS is enabled on every /api/v1/* endpoint, so the API can be called from browsers as well as servers.

POST/api/v1/chat/completionsChat completion (OpenAI-compatible, optional streaming)
POST/api/v1/images/generationsGenerate an image (synchronous)
GET/api/v1/images/{imageId}Download a generated image
POST/api/v1/videos/generationsStart a video generation (async)
GET/api/v1/videos/generations/{runId}Poll video generation status
GET/api/v1/videos/{videoId}Download a generated video
GET/api/v1/modelsList available chat + image + video models
GET/api/v1/creditsCredit balance, tier, and reset date
GET/api/v1/openapi.jsonOpenAPI 3.1 specification (no auth)

Authentication

Create an API key in Settings → API Keys (or at /settings/api-keys). Keys look like dgo_… and are shown once at creation time — store them securely. Revoking a key disables it immediately.

Send the key on every request, either way works:

Request headers
Authorization: Bearer dgo_your_key_here

# or

X-API-Key: dgo_your_key_here

A key acts on behalf of the account that created it: every request spends that account's credits, and generated images/videos land in that account's gallery. Missing, invalid, or revoked keys return 401 with an error body (see Errors).

Credits & billing

API usage draws from the same monthly credit balance as the Durango app and appears in the same usage history.

1 credit = $0.01 USD. Balances are tracked fractionally — a cheap chat message might cost 0.02 credits.
Monthly allowance by tier: Free 10 credits · Pro 500 credits · Ultra 5,000 credits. Credits reset at the start of each billing period.
Chat cost = input tokens × model input price + output tokens × model output price (see pricing in the model list). The exact charge is returned on every non-streaming response as usage.credits_used.
Image cost is token-based when the model reports usage, with per-model fallback pricing otherwise (roughly 3–18 credits per image).
Video cost is reserved when the async run starts, then finalized from provider usage or per-model fallback pricing when the run completes.
Tier gating: some models require a minimum subscription (required_tier of pro or ultra); calling them from a lower tier returns 403. Image and video generation require Pro or Ultra.
Out of credits: requests fail with 402 insufficient_credits until your monthly reset or an upgrade.

List models

Lists every model callable through the API, with capabilities, required tier, and pricing.

GET/api/v1/modelsList available models
Query parameters
typestring
Optional filter: chat, image, or video.
Request
curl "https://durango.sh/api/v1/models?type=chat" \
  -H "Authorization: Bearer $DURANGO_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "id": "anthropic/claude-sonnet-4.5",
      "object": "model",
      "type": "chat",
      "name": "Claude Sonnet 4.5",
      "provider": "Anthropic",
      "category": "favorites",
      "capabilities": { "vision": true, "reasoning": true, "toolUse": true, "imageGen": false },
      "required_tier": "free",
      "context_length": 200000,
      "pricing": { "prompt": "0.000003", "completion": "0.000015" }
    }
  ]
}
Response fields
idstring
Pass this as model in chat / image / video requests.
typestring
chat models go to /api/v1/chat/completions; image models go to /api/v1/images/generations; video models go to /api/v1/videos/generations.
required_tierstring
free, pro, or ultra — the minimum subscription needed to call the model.
pricingobject | null
USD per token (prompt = input, completion = output), when known. Multiply by 1,000,000 for $/1M tokens.
capabilities.visionboolean
Whether the model accepts image inputs in chat messages.
video_capabilitiesobject | null
Supported durations, resolutions, aspect ratios, and frame-image inputs for video models.

Chat completions

OpenAI-compatible chat completion. Only models present in the Durango catalog are accepted (404 model_not_found otherwise).

POST/api/v1/chat/completionsCreate a chat completion
Request body
modelstringrequired
Model id from GET /api/v1/models?type=chat.
messagesarrayrequired
Conversation messages in OpenAI format (see below).
streamboolean
true to receive an SSE stream. Default false.
temperaturenumber
Sampling temperature.
max_tokensinteger
Cap on generated tokens.
top_pnumber
Nucleus sampling.
frequency_penalty / presence_penaltynumber
Repetition controls.
stopstring | string[]
Stop sequences.
seedinteger
Deterministic sampling on supporting models.
response_formatobject
e.g. {"type": "json_object"} for JSON mode.
toolsarray
OpenAI-style function tool definitions, forwarded to the model.
tool_choiceany
Tool selection directive.

Each message is { "role": ..., "content": ... } with roles system, developer, user, assistant, or tool. content is a string — or, for multimodal input on vision-capable models, an array of parts. image_url.url accepts HTTPS URLs or data URLs. Assistant messages with tool_calls and role: "tool" results are supported for function-calling loops.

Multimodal message
{
  "role": "user",
  "content": [
    { "type": "text", "text": "What is in this image?" },
    { "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0..." } }
  ]
}
Request
curl https://durango.sh/api/v1/chat/completions \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-4.5",
    "messages": [
      { "role": "system", "content": "You are a concise assistant." },
      { "role": "user", "content": "Name three uses for a paperclip." }
    ],
    "max_tokens": 200
  }'
Response
{
  "id": "gen-abc123",
  "object": "chat.completion",
  "created": 1750000000,
  "model": "anthropic/claude-sonnet-4.5",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "1. Holding papers..." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 28,
    "completion_tokens": 47,
    "total_tokens": 75,
    "credits_used": 0.0789
  }
}

The response is the OpenAI chat.completion shape, extended with usage.credits_used — the exact number of Durango credits charged for the request.

Streaming

Set "stream": true. The response is text/event-stream: a sequence of data: {json} lines, each an OpenAI chat.completion.chunk with incremental choices[0].delta.content, ending with data: [DONE]. A final chunk carries usage; credits are deducted when the stream completes.

Request
curl -N https://durango.sh/api/v1/chat/completions \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "openai/gpt-5.2", "stream": true, "messages": [{"role": "user", "content": "Write a haiku about rivers."}]}'
Stream
data: {"id":"gen-x","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Sil"}}]}

data: {"id":"gen-x","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"ver water"}}]}

data: {"id":"gen-x","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":19}}

data: [DONE]

OpenAI SDKs

Because the endpoint is OpenAI-compatible, official OpenAI SDKs work by overriding the base URL.

Python
from openai import OpenAI

client = OpenAI(
    base_url="https://durango.sh/api/v1",
    api_key="dgo_your_key_here",
)

completion = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.5",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(completion.choices[0].message.content)
JavaScript / TypeScript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://durango.sh/api/v1",
  apiKey: process.env.DURANGO_API_KEY,
});

const completion = await client.chat.completions.create({
  model: "google/gemini-3-pro-preview",
  messages: [{ role: "user", content: "Hello!" }],
  stream: true,
});

for await (const chunk of completion) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Image generation

Generates an image with one model, saves it to your Durango gallery, and returns it. The call is synchronous: it responds when generation finishes, typically 10–60 seconds, so use a generous HTTP timeout (120s+ recommended). Requires a Pro or Ultra subscription (403 pro_required otherwise).

POST/api/v1/images/generationsGenerate an image
Request body
modelstringrequired
Model id from GET /api/v1/models?type=image.
promptstringrequired
Description of the image to generate.
aspect_ratiostring
1:1 (default), 16:9, 9:16, 4:3, 3:4, …
image_sizestring
1K (default) or 2K.
response_formatstring
url (default) or b64_json to additionally inline base64 image bytes.
source_imagesstring[]
Up to 4 reference images for image-to-image editing; each an HTTPS URL or data URL, max 10 MB.
privateboolean
true keeps the image out of public surfaces. Default false.
Request
curl https://durango.sh/api/v1/images/generations \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 180 \
  -d '{
    "model": "google/gemini-3-pro-image-preview",
    "prompt": "A watercolor painting of a lighthouse at dawn",
    "aspect_ratio": "16:9",
    "response_format": "b64_json"
  }'
Response
{
  "id": "run_abc123",
  "created": 1750000000,
  "model": "google/gemini-3-pro-image-preview",
  "data": [
    {
      "id": "img_xyz789",
      "url": "https://durango.sh/api/v1/images/img_xyz789",
      "b64_json": "iVBORw0KGgoAAAANS...",
      "content_type": "image/png"
    }
  ],
  "text": null,
  "usage": { "credits_used": 12 }
}
data[].url requires the same API-key authentication to download — it is not a public link. Use response_format: "b64_json" if you want the bytes inline.
text carries any accompanying text the model produced (some models narrate their generations).
Failures (content-policy refusals, model errors) return 502 generation_failed with the upstream message; no credits are charged for failed generations.

Image download

Returns the raw image bytes for any image generated by your account.

GET/api/v1/images/{imageId}Download a generated image
Request
curl https://durango.sh/api/v1/images/img_xyz789 \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -o lighthouse.png

Video generation

Starts an asynchronous video generation run, saves completed videos to your Durango gallery, and returns a poll URL. Requires a Pro or Ultra subscription (403 pro_required otherwise).

POST/api/v1/videos/generationsStart a video generation
Request body
modelstringrequired
Model id from GET /api/v1/models?type=video.
model_idsstring[]
Optional multi-model run; model is included with these ids.
promptstringrequired
Description of the video to generate.
aspect_ratiostring
16:9 (default), 9:16, 1:1, etc. depending on model support.
resolutionstring
720p (default), 1080p, 4K, etc. depending on model support.
durationnumber
Seconds, 1-15, depending on model support.
generate_audioboolean
Ask for audio when the selected model supports it.
source_imagesstring[]
Up to 2 reference images; HTTPS URL, data URL, or authenticated /api/v1/images/{id} URL from the same account.
source_image_idsstring[]
Up to 2 generated image ids from the same Durango account.
privateboolean
true keeps the video out of public surfaces. Default false.
Start request
curl https://durango.sh/api/v1/videos/generations \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/veo-3.1-fast",
    "prompt": "A slow dolly shot through a rainy neon market",
    "aspect_ratio": "16:9",
    "resolution": "720p",
    "duration": 5,
    "private": true
  }'
Start response
{
  "id": "run_abc123",
  "object": "video.generation",
  "created": 1750000000,
  "status": "running",
  "model": "google/veo-3.1-fast",
  "models": ["google/veo-3.1-fast"],
  "poll_url": "https://durango.sh/api/v1/videos/generations/run_abc123",
  "data": []
}
GET/api/v1/videos/generations/{runId}Poll video status
Completed poll response
{
  "id": "run_abc123",
  "object": "video.generation",
  "status": "completed",
  "data": [
    {
      "id": "vid_xyz789",
      "url": "https://durango.sh/api/v1/videos/vid_xyz789",
      "model": "google/veo-3.1-fast",
      "content_type": "video/mp4"
    }
  ],
  "usage": { "credits_used": 50 }
}

Video download

Returns the raw video bytes for any video in your library — generated or edited.

GET/api/v1/videos/{videoId}Download a video
Request
curl https://durango.sh/api/v1/videos/vid_xyz789 \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -o market.mp4

The route streams from object storage and supports HTTP Range requests, so players can seek and clients can resume. A satisfied range answers 206 Partial Content with Content-Range; an impossible one answers 416. Every response carries Accept-Ranges: bytes. The same applies to /api/v1/videos/uploads/{id}/content.

Partial download
curl https://durango.sh/api/v1/videos/vid_xyz789 \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Range: bytes=0-1048575" \
  -o first-mb.part

Video library

Lists the videos in your library, newest first.

GET/api/v1/videosList your videos
Query parameters
limitquery
How many videos to return. Default 20, max 200.
Response
{
  "object": "list",
  "data": [
    {
      "id": "vid_xyz789",
      "url": "/api/v1/videos/vid_xyz789",
      "duration_seconds": 44.7,
      "width": 1080,
      "height": 1920,
      "content_type": "video/mp4",
      "byte_size": 9876543,
      "prompt": "Create a 45s highlights reel",
      "model": "durango/video-edit",
      "kind": "edit",
      "created_at": 1750000000000
    }
  ]
}

kind is generation for model generations and edit for outputs of the video editor.

Video uploads

Bring your own footage into Durango so it can be edited. Upload a file directly, or hand Durango a URL — including HLS and DASH manifests, which are normalized to MP4 on ingest.

POST/api/v1/videos/uploadsUpload a video
Request body
urlstring
JSON mode. A direct video file (mp4/webm/mov) or an .m3u8 / .mpd manifest.
namestring
Optional display name, in either mode.
filefile part
Multipart mode. Send Content-Type: multipart/form-data with the video in the file field.
Request
# From a URL
curl https://durango.sh/api/v1/videos/uploads \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/roll1.mp4", "name": "roll1.mp4" }'

# From a local file
curl https://durango.sh/api/v1/videos/uploads \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -F "file=@roll1.mp4"
Response (201)
{
  "id": "upl_abc123",
  "object": "video.upload",
  "name": "roll1.mp4",
  "content_type": "video/mp4",
  "byte_size": 12345678,
  "duration_seconds": 182.4,
  "width": 1920,
  "height": 1080,
  "created_at": 1750000000000,
  "url": "/api/v1/videos/uploads/upl_abc123/content"
}
GET/api/v1/videos/uploadsList uploads (?limit=, default 50)
GET/api/v1/videos/uploads/{id}Get one upload
GET/api/v1/videos/uploads/{id}/contentDownload the raw bytes
DELETE/api/v1/videos/uploads/{id}Delete the upload and its stored object
Uploads are capped at 2 GB (413 upload_too_large) and must contain a readable video stream (400 invalid_video otherwise). Duration and dimensions are probed at ingest.
URL ingest only follows public hosts — private and loopback addresses are rejected with 400 url_not_allowed. For an HLS manifest the same check applies to every segment, init segment and key URI inside the playlist; master playlists ingest their highest-bandwidth variant.
Uploads are rate limited to 30 per hour per account (429 rate_limit_exceeded, honour Retry-After). The name field is normalized on the way in: control characters are stripped and it is capped at 200 characters.
Deleting an upload that a queued, planning, or rendering edit still references is refused with 409 upload_in_use — wait for the edit to settle first.

Video editing

Starts an asynchronous edit over your videos. Describe the result and Durango watches the footage, plans the cuts with a chat model, and renders an H.264/AAC MP4. Requires a Pro or Ultra subscription (403 pro_required otherwise).

How planning works. A prompt-only edit runs three stages before anything renders. Probe reads duration, resolution, audio, and samples scene changes and loudness. Vision samples frames across each source (every 2 seconds on short footage, widening to at most ~8 seconds on long sources, with the source timestamp burned into each frame) and shows them to a vision model together with your prompt, which returns timestamped, labelled events scored 1–10 for how well each matches what you asked for — this is what lets the planner find a slow submission or a quiet reaction, which motion and loudness alone cannot see. It is the slow stage, and it is what progress 0.2–0.35 reports. Plan then builds the timeline from the highest-scoring relevant events, pads each one (~1–2s before, ~2–3s after), keeps them chronological, and respects output.max_duration.

Vision works to a fixed budget, so a long or many-source edit costs what a short one does: only the first 30 minutes of each source is looked at, the frame budget is shared across all sources in proportion to their duration, and the stage stops when its wall-clock budget runs out — sources it did not reach are planned from motion and loudness instead. If the vision model keeps refusing the request (it does not accept images, or the provider fails repeatedly) the pass is abandoned rather than retried chunk by chunk, and the reason appears in analysis.warnings.

The detected events come back on the edit object as analysis, so you can see what the planner was working from. If the planner model fails, the plan is built directly from the top-scored events; if the vision stage fails too, planning falls back to the scene-change/loudness heuristic. Either way the edit still renders, and plan.planner tells you which path ran. Vision tokens are included in the per-second edit price — there is no separate charge.

POST/api/v1/videos/editsStart a video edit
Request body
source_video_idsstring[]required
1–10 upload ids or generated video ids. The order defines source_index in the plan.
promptstring
What the edit should produce. Required unless operations is supplied.
output.aspect_ratiostring
original (default), 9:16, 16:9, 1:1
output.resolutionstring
original (default), 720p, 1080p
output.max_durationnumber
Target/maximum output length in seconds (1–600). Omit it on a prompt-only edit and a 60 second cap applies.
output.formatstring
mp4 — the only value in v1.
operationsarray
Explicit edit plan. When present, no AI planning happens.
modelstring
Planner chat model id. Defaults to a fast Durango default. Must be a chat model (400 invalid_planner_model otherwise).
vision_modelstring
Chat model used for the vision content-understanding pass. Defaults to a fast multimodal Durango default. Must be a chat model listed with capabilities.vision: true — an unknown id is 404 model_not_found, one that does not accept image input is 400 invalid_vision_model.
webhook_urlstring
Public https URL that receives a POST of the terminal edit object. https is required and redirects are not followed.
Start request
curl https://durango.sh/api/v1/videos/edits \
  -H "Authorization: Bearer $DURANGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source_video_ids": ["upl_abc123"],
    "prompt": "Create a 45s highlights reel of the best action moments",
    "output": { "aspect_ratio": "9:16", "resolution": "1080p", "max_duration": 45 }
  }'
Start response (202)
{
  "id": "edt_abc123",
  "object": "video.edit",
  "status": "queued",
  "created_at": 1750000000000,
  "poll_url": "https://durango.sh/api/v1/videos/edits/edt_abc123"
}
GET/api/v1/videos/edits/{id}Poll edit status

status moves queuedplanningrendering completed / failed, with progress between 0 and 1. Results are stored as normal videos, so they appear in GET /api/v1/videos and download from GET /api/v1/videos/{id}.

Completed poll response
{
  "id": "edt_abc123",
  "object": "video.edit",
  "status": "completed",
  "progress": 1,
  "prompt": "Create a 45s highlights reel of the best action moments",
  "source_video_ids": ["upl_abc123"],
  "output": { "aspect_ratio": "9:16", "resolution": "1080p", "max_duration": 45, "format": "mp4" },
  "plan": { "operations": [{ "op": "clip", "source_index": 0, "start": 12.5, "end": 19 }], "planner": "ai" },
  "analysis": {
    "vision": { "model": "google/gemini-3-flash-preview", "frames": 96, "chunks_annotated": 3, "chunks_total": 3, "prompt_tokens": 41200, "completion_tokens": 980 },
    "sources": [
      {
        "source_index": 0,
        "duration_seconds": 192.4,
        "width": 1920,
        "height": 1080,
        "event_count": 7,
        "events": [
          { "start": 14, "end": 19, "label": "armbar submission", "significance": 9, "description": "Locks in an armbar and the opponent taps." }
        ]
      }
    ]
  },
  "data": [
    {
      "id": "vid_xyz789",
      "url": "/api/v1/videos/vid_xyz789",
      "duration_seconds": 44.7,
      "width": 1080,
      "height": 1920,
      "content_type": "video/mp4",
      "byte_size": 9876543
    }
  ],
  "error": null,
  "credits_used": 135,
  "created_at": 1750000000000,
  "updated_at": 1750000060000,
  "completed_at": 1750000060000
}
GET/api/v1/videos/editsList edits (?limit=, default 20)

Editing costs 2 credits per second of rendered output (minimum 5, ×1.5 at 1080p). Credits are reserved when the job is accepted — against the exact plan length when you send operations, against output.max_duration (default 60s) otherwise — and settled against the real output length, with the unused part returned. A failed edit is refunded in full; an edit interrupted by a server restart is reaped and refunded within ~30 minutes of its last progress update, and any edit still unfinished after about two hours is cancelled and refunded regardless.

An account may hold 10 edits in progress at once; one more returns 429 too_many_active_edits. The limit is a deployment setting (DURANGO_MAX_ACTIVE_EDITS) — ask your Durango operator to raise it if one key fronts many end-users. Renders are queued server-side, so an accepted job can sit at status: "queued" before it starts. When webhook_url is set, Durango POSTs the terminal edit object once — delivery is best-effort and not retried, so keep polling as the source of truth.

Edit operations

The plan IR the AI planner emits — and that you can send yourself for a deterministic edit.

cliprequired
{"op":"clip","source_index":0,"start":12.5,"end":19} — take seconds [start,end) from a source. The output timeline is the clips in order. At least one is required.
reframemax 1
{"op":"reframe","aspect_ratio":"9:16","strategy":"center"} — scale and centre-crop the whole timeline.
speedmax 1
{"op":"speed","factor":1.5,"scope":"all"} — 0.25× to 4×.
audiomax 1
{"op":"audio","mode":"keep"}keep (default) or mute.
textmax 8
{"op":"text","content":"...","position":"bottom","start":0,"end":3} — overlay timed against the output timeline; position is top, center, or bottom.

Clip bounds are clamped to the real source durations and the timeline is trimmed to output.max_duration (or the 60-second default cap on a prompt-only edit), so a render can never run past the length credits were reserved against. Malformed operations return 400 invalid_operations with a message naming the offending index.

Command line

The durango CLI wraps this API, including the full upload → edit → download flow.

Install
npm install -g durango
durango login            # or: export DURANGO_API_KEY=dgo_...
durango whoami           # identity, tier, credit balance

Every command accepts --json (exactly one JSON object on stdout, human text suppressed), --base-url, and --api-key, which makes the CLI safe to drive from scripts and coding agents. durango help --json prints a machine-readable schema of every command and flag.

Video editing from the CLI
# Bring footage in (local file or a URL / HLS / DASH manifest)
durango video upload ./roll1.mp4
durango video upload --url https://example.com/master.m3u8 --name roll1.mp4
durango uploads list
durango uploads delete <id>

# Edit it — uploads the local file first, waits for the render, saves the result
durango video edit \
  --source-file ./roll1.mp4 \
  --prompt "45s highlights reel of the best action moments" \
  --aspect-ratio 9:16 --resolution 1080p --max-duration 45 \
  --out highlights.mp4

# Or against ids you already have, without blocking
durango video edit --source <id> --prompt "..." --no-wait
durango video edits status <id>
durango video edits list
durango video list

Credit balance

Returns the authenticated account's remaining credits, monthly limit, subscription tier, and period end.

GET/api/v1/creditsGet credit balance
Request
curl https://durango.sh/api/v1/credits \
  -H "Authorization: Bearer $DURANGO_API_KEY"
Response
{
  "object": "credits",
  "balance": 412.37,
  "limit": 500,
  "tier": "pro",
  "period_end": 1751328000000
}

Errors

All errors use a single envelope.

Error envelope
{
  "error": {
    "message": "Human-readable explanation.",
    "type": "invalid_request_error",
    "code": "model_not_found"
  }
}
401missing_api_key
No key in Authorization / X-API-Key header.
401invalid_api_key
Key is malformed, unknown, or revoked.
402insufficient_credits
Credit balance is 0 — upgrade or wait for the monthly reset.
403premium_model_required
Model needs a Pro / Ultra subscription.
403pro_required
Image generation, video generation, or video editing needs a Pro / Ultra subscription.
400model_required, invalid_messages, …
Malformed request — the message explains exactly what.
400invalid_operations
An edit operation is malformed — the message names the index.
400invalid_planner_model
model is not a chat model — list them via GET /api/v1/models?type=chat.
400invalid_vision_model
vision_model does not accept image input — pick one with capabilities.vision: true from GET /api/v1/models?type=chat.
400invalid_webhook_url
webhook_url is not a public https URL.
400url_not_allowed, url_not_video, invalid_video
Upload source is unreachable, private, or not a readable video.
404model_not_found
Model id is not in the catalog — check GET /api/v1/models.
404image_not_found
Image id unknown or owned by another account.
404video_generation_not_found, video_not_found
Video run/id unknown or owned by another account.
404upload_not_found, video_edit_not_found, source_video_not_found
Upload/edit/source id unknown or owned by another account.
409upload_in_use
An in-progress edit still references the upload you tried to delete.
413upload_too_large
Upload exceeds the 2 GB cap.
416range_not_satisfiable
The requested byte range lies outside the video.
429rate_limit_exceeded
Too many uploads this hour — wait for Retry-After.
429too_many_active_edits
10 edits are already in progress on this account.
4xxupstream_error
The upstream model provider rejected the request (status passed through).
502upstream_unreachable, generation_failed
Upstream failure — safe to retry.

For AI agents

Pointers for autonomous agents working against this API.

Discover capabilities in this order: GET /api/v1/openapi.json (schema) → GET /api/v1/models (what you can call) → GET /api/v1/credits (budget).
Always send Content-Type: application/json on POSTs.
Prefer stream: false unless you need incremental output; the non-streaming response includes exact credits_used.
Image generation is slow (10–60s) — set client timeouts to at least 120 seconds and do not retry while a request is in flight.
Video generation is asynchronous — start the run once, poll poll_url, and download data[].url after completion.
Video editing has the same shape: POST /api/v1/videos/edits → poll poll_url every 3–5 seconds → download data[0].url. To edit footage you did not generate, upload it first via POST /api/v1/videos/uploads and pass the returned id in source_video_ids.
Send operations instead of prompt when you already know the cuts you want — it is deterministic, skips the planner, and returns the same result shape.
Set output.max_duration explicitly on prompt-only edits: without it the output is capped at 60 seconds and credits are reserved against that cap.
Treat 402 as terminal until the human upgrades or credits reset; treat 502 as retryable. Treat 429 as backoff-and-retry — too_many_active_edits clears as your other edits finish, rate_limit_exceeded after Retry-After.
This entire reference is available as plain markdown at https://durango.sh/docs/llms.txt.