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
https://durango.shAll 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.
/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:
Authorization: Bearer dgo_your_key_here
# or
X-API-Key: dgo_your_key_hereA 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.
pricing in the model list). The exact charge is returned on every non-streaming response as usage.credits_used.required_tier of pro or ultra); calling them from a lower tier returns 403. Image and video generation require Pro or Ultra.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.
/api/v1/models— List available modelstypestringchat, image, or video.curl "https://durango.sh/api/v1/models?type=chat" \
-H "Authorization: Bearer $DURANGO_API_KEY"{
"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" }
}
]
}idstringmodel in chat / image / video requests.typestringchat models go to /api/v1/chat/completions; image models go to /api/v1/images/generations; video models go to /api/v1/videos/generations.required_tierstringfree, pro, or ultra — the minimum subscription needed to call the model.pricingobject | nullprompt = input, completion = output), when known. Multiply by 1,000,000 for $/1M tokens.capabilities.visionbooleanvideo_capabilitiesobject | nullChat completions
OpenAI-compatible chat completion. Only models present in the Durango catalog are accepted (404 model_not_found otherwise).
/api/v1/chat/completions— Create a chat completionmodelstringrequiredGET /api/v1/models?type=chat.messagesarrayrequiredstreambooleantrue to receive an SSE stream. Default false.temperaturenumbermax_tokensintegertop_pnumberfrequency_penalty / presence_penaltynumberstopstring | string[]seedintegerresponse_formatobject{"type": "json_object"} for JSON mode.toolsarraytool_choiceanyEach 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.
{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this image?" },
{ "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0..." } }
]
}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
}'{
"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.
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."}]}'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.
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)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).
/api/v1/images/generations— Generate an imagemodelstringrequiredGET /api/v1/models?type=image.promptstringrequiredaspect_ratiostring1:1 (default), 16:9, 9:16, 4:3, 3:4, …image_sizestring1K (default) or 2K.response_formatstringurl (default) or b64_json to additionally inline base64 image bytes.source_imagesstring[]privatebooleantrue keeps the image out of public surfaces. Default false.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"
}'{
"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).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.
/api/v1/images/{imageId}— Download a generated imagecurl https://durango.sh/api/v1/images/img_xyz789 \
-H "Authorization: Bearer $DURANGO_API_KEY" \
-o lighthouse.pngVideo 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).
/api/v1/videos/generations— Start a video generationmodelstringrequiredGET /api/v1/models?type=video.model_idsstring[]promptstringrequiredaspect_ratiostring16:9 (default), 9:16, 1:1, etc. depending on model support.resolutionstring720p (default), 1080p, 4K, etc. depending on model support.durationnumbergenerate_audiobooleansource_imagesstring[]source_image_idsstring[]privatebooleantrue keeps the video out of public surfaces. Default false.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
}'{
"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": []
}/api/v1/videos/generations/{runId}— Poll video status{
"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 generated by your account.
/api/v1/videos/{videoId}— Download a generated videocurl https://durango.sh/api/v1/videos/vid_xyz789 \
-H "Authorization: Bearer $DURANGO_API_KEY" \
-o market.mp4Credit balance
Returns the authenticated account's remaining credits, monthly limit, subscription tier, and period end.
/api/v1/credits— Get credit balancecurl https://durango.sh/api/v1/credits \
-H "Authorization: Bearer $DURANGO_API_KEY"{
"object": "credits",
"balance": 412.37,
"limit": 500,
"tier": "pro",
"period_end": 1751328000000
}Errors
All errors use a single envelope.
{
"error": {
"message": "Human-readable explanation.",
"type": "invalid_request_error",
"code": "model_not_found"
}
}missing_api_keyinvalid_api_keyinsufficient_creditspremium_model_requiredpro_requiredmodel_required, invalid_messages, …model_not_foundimage_not_foundvideo_generation_not_found, video_not_foundupstream_errorupstream_unreachable, generation_failedFor AI agents
Pointers for autonomous agents working against this API.
GET /api/v1/openapi.json (schema) → GET /api/v1/models (what you can call) → GET /api/v1/credits (budget).Content-Type: application/json on POSTs.stream: false unless you need incremental output; the non-streaming response includes exact credits_used.poll_url, and download data[].url after completion.402 as terminal until the human upgrades or credits reset; treat 502 as retryable.