Introduction
AIport is a unified AI API gateway for Indonesian developers — one API key, one IDR balance, every modality.
Why AIport.
- Top up in IDR. No FX markup, no monthly fee, no expiry. Each call deducts exactly what it costs from your balance.
- One key, every modality. Text, image, image-to-image, and video generation behind a single
Authorization: Bearerheader. - Provider routing & auto-refund. When a provider errors mid-job, credits are refunded automatically.
- OpenAI-compatible text endpoint.
POST /v1/chat/completionsis a drop-in for the OpenAI SDK, includingstream: true.
Base URL
https://aiport.id
All endpoints are JSON unless noted. Async modalities (image, img2img, video) return a job_id you poll via GET /v1/jobs/{job_id}.
Authentication
Every /v1/* call requires a Bearer token. Create one in your dashboard at /dashboard/api-keys.
Send your API key in the Authorization header:
Authorization: Bearer aip_live_...
Keys are scoped to your account and bill against your IDR balance. Rotate compromised keys immediately by deleting and re-creating from the dashboard. Never embed an API key in client-side code — proxy through your own backend.
Quickstart
Every endpoint is plain HTTP + JSON. The only thing that varies is whether the response is synchronous or asynchronous.
Sync vs async — read this first.
Every AIport endpoint is a normal HTTP request: POST (or GET for jobs), Authorization: Bearer header, JSON body. The difference is only in how the result comes back:
- Synchronous —
/v1/chat/completions(alias/v1/text/completions). The response body *is* the result, unless you opt intostream: true. Typical latency: 1–10 s. - Asynchronous —
/v1/image/generate,/v1/image/img2img, all/v1/video/*. The POST returns immediately with{ "job_id": "...", "status": "pending" }. You then pollGET /v1/jobs/{job_id}every few seconds untilstatusbecomesdone(readresult_url) orfailed. Typical latency: 30–120 s for video, 5–30 s for image.
Async exists because images and videos can take a minute or more — too long to hold open a single HTTP request reliably.
1. Synchronous: text completion
The text endpoint is OpenAI-compatible — point the OpenAI SDK at https://aiport.id/v1, or call it directly:
curl -X POST https://aiport.id/v1/chat/completions \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"Hello"}]}'Response (200) — final answer is already inside choices[0].message.content:
{ "choices": [ { "message": { "role": "assistant", "content": "Hi!" } } ], "usage": { ... } }Add "stream": true to receive the answer incrementally as Server-Sent Events (OpenAI chat.completion.chunk objects, ending with data: [DONE]). See the Text completions section for the chunk shape.
2. Asynchronous: image generation — step by step
Step 2a — POST the request. Same shape as text, just a different URL:
curl -X POST https://aiport.id/v1/image/generate \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-image-2","prompt":"Sunset over Mount Bromo","resolution":"2k"}'Response (202) — note: no image yet, only a job id to poll:
{ "job_id": "job_01HX...", "status": "pending" }Step 2b — GET the job until it's done:
curl https://aiport.id/v1/jobs/job_01HX... \ -H "Authorization: Bearer $AIPORT_API_KEY"
Response (200) — when finished, status is done and result_url points at the image. Its host can vary between jobs and isn't guaranteed to be under aiport.id — don't hardcode a domain allowlist against it:
{ "job_id": "job_01HX...", "status": "done", "result_url": "https://result.example/..." }3. Same thing, scripted with a poll loop (optional)
The block below is just the two-step flow wrapped in a shell loop — useful for trying things in a terminal:
# Submit
JOB=$(curl -s -X POST https://aiport.id/v1/image/generate \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-image-2","prompt":"Sunset over Mount Bromo","resolution":"2k"}' | jq -r .job_id)
# Poll every 3s
while true; do
STATUS=$(curl -s https://aiport.id/v1/jobs/$JOB \
-H "Authorization: Bearer $AIPORT_API_KEY")
echo "$STATUS"
case "$(echo $STATUS | jq -r .status)" in
done|failed) break ;;
esac
sleep 3
doneIn production, replace the shell loop with a job-polling routine in your language of choice (or a webhook on your side if you don't want to poll).
Text completions
Chat-style generation with an OpenAI-compatible request/response shape. POST /v1/chat/completions (alias: /v1/text/completions). Synchronous by default, or set stream: true for Server-Sent Events. GET /v1/models lists the available text models for OpenAI-compatible clients.
/v1/chat/completionsChat-style text generation. OpenAI-compatible request and response shapes — a drop-in for the OpenAI SDK (set base_url to https://aiport.id/v1). The legacy path /v1/text/completions is a backward-compatible alias.
none low medium highauto none requiredcurl -X POST https://aiport.id/v1/chat/completions \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "user", "content": "Write a haiku about Jakarta traffic."}
]
}'{
"id": "chatcmpl_...",
"object": "chat.completion",
"created": 1716480000,
"model": "deepseek-v4-flash",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 38,
"total_tokens": 50,
"prompt_tokens_details": {"cached_tokens": 0},
"completion_tokens_details": {"reasoning_tokens": 0}
}
}Synchronous unless stream is true. prompt_tokens_details.cached_tokens reflects prompt-cache hits (billed at a reduced input rate when the upstream reports them, currently DeepSeek); completion_tokens_details.reasoning_tokens counts reasoning output. Standard OpenAI params (top_p, stop, presence_penalty, frequency_penalty, seed, …) are forwarded verbatim to OpenAI-compatible upstreams; unknown params are dropped.
With stream: true the response is text/event-stream. Each event is a chat.completion.chunk: the first carries delta.role, the rest carry delta.content, then a chunk with finish_reason: stop, a final usage-only chunk (choices: []), and the data: [DONE] terminator. If the upstream fails mid-stream the connection ends with an in-band error event — there is no provider switch once the first byte is sent, and a stream that already emitted output is not refunded.
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1716480000,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1716480000,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{"content":"Hi"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1716480000,"model":"deepseek-v4-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1716480000,"model":"deepseek-v4-flash","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":38,"total_tokens":50,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":0}}}
data: [DONE]400 invalid_request_errormessages missing, content too long, invalid temperature, or reasoning_effort on a non-reasoning model.400 upstream_invalid_requestThe upstream model rejected the request's parameters (e.g. tools with a non-"none" reasoning_effort on a GPT reasoning model). The message explains why; not retryable as-is. Refunded.401 unauthorizedMissing or invalid API key.404 MODEL_NOT_FOUNDRequested model id does not exist or is disabled.429 insufficient_quotaIDR balance cannot cover the request — top up to continue.502 provider_errorUpstream AI provider failed. Refunded unless a stream already emitted output. A stream that fails before its first chunk returns a normal JSON error with the matching HTTP status instead of an SSE event.503 provider_unavailableEvery upstream provider for the model is down or failing. Refunded; safe to retry later.
/v1/modelsList the text/chat models available to your key, in the OpenAI models shape. OpenAI-compatible clients call this to verify the base URL and populate model pickers. GET /v1/models/{model} retrieves a single model.
curl https://aiport.id/v1/models \ -H "Authorization: Bearer $AIPORT_API_KEY"
{
"object": "list",
"data": [
{
"id": "deepseek-v4-flash",
"object": "model",
"created": 1716480000,
"owned_by": "aiport"
}
]
}Only enabled text/chat models are returned — the same ids you can pass as model to /v1/chat/completions. Image and video models are not listed here (those modalities are async and not OpenAI-compatible). created is a Unix timestamp in seconds. GET /v1/models/{model} returns a single model object, or 404 model_not_found if the id is unknown, disabled, or not a text model.
401 unauthorizedMissing or invalid API key.404 model_not_foundGET /v1/models/{model}: id does not exist, is disabled, or is not a text model.
Tool calling
Let a model call your functions. Works on /v1/chat/completions with the standard OpenAI tools / tool_choice fields — pass tool definitions, run the call yourself, feed the result back. Supported across OpenAI-compatible, DeepSeek, Gemini, and Claude models.
Tool calling is a round-trip you drive. You describe the functions the model is allowed to call; the model replies with the function name and JSON arguments; you execute it and send the result back; the model uses that result to answer.
1. Send a request with tools.
curl -X POST https://aiport.id/v1/chat/completions \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "user", "content": "What is the weather in Jakarta?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
}
]
}'2. The model asks to call the tool. Instead of content, the assistant message carries tool_calls and finish_reason is tool_calls. arguments is a JSON string you must parse:
{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Jakarta\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}3. Run the function and reply with a tool message. Append the assistant's tool_calls message verbatim, then one role: "tool" message per call, echoing its tool_call_id. Keep sending tools on this follow-up request too — resubmit it on every turn of the conversation, exactly as the OpenAI SDK does, or some models reject the replayed tool history:
curl -X POST https://aiport.id/v1/chat/completions \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{"role": "user", "content": "What is the weather in Jakarta?"},
{"role": "assistant", "content": null, "tool_calls": [
{"id": "call_abc123", "type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\":\"Jakarta\"}"}}
]},
{"role": "tool", "tool_call_id": "call_abc123", "content": "{\"temp_c\":31,\"sky\":\"partly cloudy\"}"}
],
"tools": [
{"type": "function", "function": {"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"]}}}
]
}'The model now replies with normal content (finish_reason: "stop"):
{ "choices": [ { "message": { "role": "assistant", "content": "It's 31°C and partly cloudy in Jakarta." }, "finish_reason": "stop" } ] }Forcing or disabling calls. Use tool_choice: "auto" (default when tools is set) lets the model decide, "required" forces some tool call, "none" forces a plain-text reply, and { "type": "function", "function": { "name": "get_weather" } } forces one specific function. Set parallel_tool_calls: false to limit the model to one call per turn.
Claude models. Claude Sonnet 5.5, Opus 5.5, and Fable 5.1 don't support forced tool calls. On these models "required" and a named function are sent as "auto" plus a system instruction telling the model to call the tool. The model almost always follows it, but check finish_reason rather than assuming a tool_calls reply. Other Claude models (Haiku 4.5, Sonnet 5, Opus 5) honor forced calls normally.
Streaming. With stream: true, tool calls arrive incrementally on delta.tool_calls (each fragment carries an index; function.arguments is concatenated across chunks). Accumulate by index until the terminating chunk with finish_reason: "tool_calls".
Round-trip integrity. Send each assistant tool_calls message back exactly as you received it — including the id on every call — and pair it with a matching tool_call_id in your tool reply. Some models (e.g. Gemini reasoning models) attach opaque state to the call id that must survive the round-trip, so don't rewrite or drop it.
Image generation
Text-to-image. Async — returns a job_id; poll /v1/jobs/{job_id} for the result. Pricing varies by resolution tier on supporting models.
/v1/image/generateText-to-image. Returns an async job_id — poll GET /v1/jobs/{job_id} until status is done.
1:1 3:2 2:3 4:3 3:4 16:9 9:161k 2k 4kcurl -X POST https://aiport.id/v1/image/generate \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "A traditional Balinese temple at golden hour, cinematic",
"aspect_ratio": "16:9",
"resolution": "2k"
}'{
"job_id": "job_01HX...",
"status": "pending"
}Poll GET /v1/jobs/{job_id} every 2–5 seconds until status is 'done' (then read result_url) or 'failed'. Image generation typically takes 10–60 seconds at 1k, 30–120 seconds at 2k, and up to 180 seconds at 4k. Jobs that exceed 10 minutes are auto-failed and credits are refunded.
400 invalid_requestMissing prompt, invalid aspect_ratio, or invalid resolution.400 invalid_requestUnrecognized field in the request body (e.g. a typo or deprecated field name).401 unauthorizedMissing or invalid API key.402 insufficient_balanceIDR balance cannot cover the request.404 MODEL_NOT_FOUNDRequested model id does not exist or is disabled.
Image-to-image
Edit or transform an existing image. Upload reference images first (or pass any HTTPS URL), then call /v1/image/img2img.
/v1/image/uploadUpload a reference image to be used by /v1/image/img2img. Returns a hosted URL — pass that URL (not an id) in the img2img call's images field.
curl -X POST https://aiport.id/v1/image/upload \ -H "Authorization: Bearer $AIPORT_API_KEY" \ -F "file=@./reference.jpg;type=image/jpeg"
{
"url": "https://cdn.aiport.id/uploads/...",
"type": "image"
}No upload_id — the returned url is what you pass to img2img or other endpoints that accept a reference image URL. The server also detects the file type from its content, so a plain -F "file=@./reference.jpg" (no ;type=) works too — the explicit ;type= above is only needed if your client sends application/octet-stream for this extension (some curl builds do this for .webp).
400File content does not match a declared, specific Content-Type.401 unauthorizedMissing or invalid API key.413File exceeds the per-modality size limit.415Unsupported or undetectable file type (not JPEG, PNG, or WebP).
/v1/image/img2imgImage-to-image editing. Provide reference image URL(s) (from /v1/image/upload or any HTTPS URL) plus a prompt. Returns an async job_id.
1:1 3:2 2:3 4:3 3:4 16:9 9:161k 2k 4kcurl -X POST https://aiport.id/v1/image/img2img \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Make it look like a Studio Ghibli scene",
"images": ["https://cdn.aiport.id/uploads/.../ref.jpg"],
"resolution": "2k"
}'{ "job_id": "job_01HX...", "status": "pending" }Poll GET /v1/jobs/{job_id} every 2–5 seconds. Image-to-image typically takes 10–60 seconds at 1k and 30–180 seconds at higher resolutions. Jobs that exceed 10 minutes are auto-failed and credits are refunded.
400Missing prompt, invalid image URL, or unsupported model parameter.400 invalid_requestUnrecognized field in the request body (e.g. a typo or deprecated field name).401 unauthorizedMissing or invalid API key.402 insufficient_balanceIDR balance cannot cover the request.
Video generation
Cinematic video generation, billed per generated second by output resolution. Three flavors: text-to-video, image-to-video, and multimodal (multiple references).
/v1/video/generateText-to-video. Async — returns job_id, then poll /v1/jobs/{job_id} until done. Billing is per generated second by output resolution.
480p 720p 1080p 2k 4k4 5 6 7 8 9 10 11 12 13 14 15adaptive 16:9 4:3 1:1 3:4 9:16 21:9curl -X POST https://aiport.id/v1/video/generate \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.0-fast",
"prompt": "A drone shot flying over rice terraces at sunrise",
"resolution": "1080p",
"duration": 6,
"ratio": "16:9"
}'{ "job_id": "job_01HX...", "status": "pending" }Video jobs typically take 30–120 seconds. Poll every 3–5 seconds.
400Invalid resolution, duration, ratio, or missing prompt.400 invalid_requestUnrecognized field in the request body (e.g. a typo or deprecated field name).401 unauthorizedMissing or invalid API key.402 insufficient_balanceIDR balance cannot cover the request.
/v1/video/img2videoImage-to-video. Provide a first frame (and optional last frame) URL; the model interpolates motion. Async job. Two request shapes depending on model: standard models (default) use the fields below. Sora 2 (`img2video-sora-2`) uses a different shape instead — `image_url` (required, https) in place of `first_frame_url`/`last_frame_url`, no `resolution` or `conversion_slots`, and `duration` restricted to 4, 8, or 12 seconds at flat per-tier pricing (not per-second). See /pricing for which tier applies to each model.
480p 720p 1080p 2k 4k4 5 6 7 8 9 10 11 12 13 14 15all firstFrameUrl lastFrameUrlcurl -X POST https://aiport.id/v1/video/img2video \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.0-fast-img2video",
"prompt": "Camera slowly pushes in, leaves drift across frame",
"first_frame_url": "https://cdn.aiport.id/uploads/.../start.jpg",
"resolution": "720p",
"duration": 5
}'{ "job_id": "job_01HX...", "status": "pending" }400Missing first_frame_url, invalid duration/resolution.400 invalid_requestUnrecognized field in the request body (e.g. a typo or deprecated field name).401 unauthorizedMissing or invalid API key.402 insufficient_balanceIDR balance cannot cover the request.
/v1/video/uploadUpload a reference video to be used by /v1/video/multimodal. Returns a hosted URL with its duration already measured — pass that URL in video_urls and you won't need video_durations for it.
curl -X POST https://aiport.id/v1/video/upload \ -H "Authorization: Bearer $AIPORT_API_KEY" \ -F "file=@./scene.mp4;type=video/mp4"
{
"url": "https://cdn.aiport.id/uploads/...",
"type": "video",
"duration_seconds": 8
}duration_seconds is measured server-side and stored against this url — pass the url in video_urls on /v1/video/multimodal without also needing video_durations for it. The server also detects the file type from its content, so a plain -F "file=@./scene.mp4" (no ;type=) works too — the explicit ;type= above is only needed if your client sends application/octet-stream for this extension (curl's own MIME table on most systems has no entry for .mp4/.mov, so it does this by default). If upload fails with "Only MP4 and MOV are supported" for a file you know is a real MP4, this is almost always the cause.
400File content does not match a declared, specific Content-Type.401 unauthorizedMissing or invalid API key.413File exceeds the per-modality size limit.415Unsupported or undetectable file type (not MP4 or MOV).422 unparseable_videoCould not read the video's duration. Re-encode with faststart and try again.400 invalid_reference_videoVideo dimensions are outside the reference-video bounds: total pixels must be 409,600–2,086,876 (roughly 480p to 1080p), each side 300–6000px.
/v1/audio/uploadUpload a reference audio clip to be used by /v1/video/multimodal. Returns a hosted URL — pass that URL in audio_urls.
curl -X POST https://aiport.id/v1/audio/upload \ -H "Authorization: Bearer $AIPORT_API_KEY" \ -F "file=@./voiceover.mp3;type=audio/mpeg"
{
"url": "https://cdn.aiport.id/uploads/...",
"type": "audio"
}No upload_id — the returned url is what you pass in audio_urls on /v1/video/multimodal. The server also detects the file type from its content, so a plain -F "file=@./voiceover.mp3" (no ;type=) works too — the explicit ;type= above is only needed if your client sends application/octet-stream for this extension (some curl builds do this for .mp3/.wav/.m4a).
400File content does not match a declared, specific Content-Type.401 unauthorizedMissing or invalid API key.413File exceeds the per-modality size limit.415Unsupported or undetectable file type (not MP3, WAV, or M4A).
/v1/video/multimodalMultimodal video generation — combine reference images, videos, and audio with a text prompt. Async job; per-resolution per-second billing with a min-billable-seconds floor (see /pricing). • seedance-2.0-fast-multimodal (default): image, video, and audio references — 480p/720p/1080p/2k/4k, 4–15s, at least one reference required. • minimax-h3-multimodal: image and audio references only (no video) — 480p/720p/1080p only, 1080p capped at 10s, references optional. • Deprecated: earlier docs used images/videos/audios for these fields. The server never reads those names — use image_urls/video_urls/audio_urls instead, or the request is now rejected with 400 invalid_request.
480p 720p 1080p 2k 4k4 5 6 7 8 9 10 11 12 13 14 15all image1 image2 image3 image4 image5 image6 image7 image8 image9 video1 video2 video3curl -X POST https://aiport.id/v1/video/multimodal \
-H "Authorization: Bearer $AIPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.0-fast-multimodal",
"prompt": "Subject from image1 walking through scene from video1",
"image_urls": ["https://cdn.aiport.id/uploads/.../subject.jpg"],
"video_urls": ["https://cdn.aiport.id/uploads/.../scene.mp4"],
"resolution": "720p",
"duration": 6,
"ratio": "16:9"
}'{ "job_id": "job_01HX...", "status": "pending" }Billing when a reference asset is supplied: • seedance-2.0-fast-multimodal: 'with reference' tier + per-duration min-billable floor — see /pricing. • minimax-h3-multimodal: flat per-resolution rate regardless of references — see /pricing.
400Too many reference assets, invalid URL, or invalid resolution/duration/ratio.400 invalid_requestUnrecognized field in the request body — e.g. images/videos/audios instead of image_urls/video_urls/audio_urls.400 unknown_reference_videoA video_urls entry has no known duration — its matching video_durations entry is missing and the URL wasn't submitted with a duration before.401 unauthorizedMissing or invalid API key.402 insufficient_balanceIDR balance cannot cover the request.
Polling jobs
Async modalities (image, img2img, video) return a job_id. Poll for status here.
/v1/jobs/{job_id}Poll an async job (image, img2img, or video). Returns the current status and — when done — the result URL.
curl https://aiport.id/v1/jobs/job_01HX... \ -H "Authorization: Bearer $AIPORT_API_KEY"
{
"job_id": "job_01HX...",
"status": "done",
"result_url": "https://result.example/...",
"error_message": null,
"created_at": 1716480000000
}status is one of: 'pending', 'processing', 'done', 'failed'. On 'failed', check error_message for details (e.g. 'Provider failed. Please try again.' or 'Job timed out after N minutes'). Credits are auto-refunded on failure. result_url's host can vary between jobs and is not guaranteed to be under the aiport.id domain — don't hardcode a domain allowlist against it, just fetch whatever URL is returned. created_at is a Unix timestamp in milliseconds.
401 unauthorizedMissing or invalid API key.404Job does not exist or does not belong to this API key's owner.
Errors
All errors return an OpenAI-style JSON body of the form { "error": { "message": "...", "type": "...", "param": null, "code": "..." } } with a meaningful HTTP status.
| Status | Meaning |
|---|---|
| 400 | Invalid request: missing or malformed parameters. |
| 401 | Missing or invalid Authorization header. |
| 402 | Insufficient IDR balance on async modalities (image, img2img, video) — top up at /dashboard/topup. |
| 404 | Resource not found (unknown model id, unknown job_id, or job belongs to another user). |
| 413 | Upload exceeded the per-modality size limit. |
| 429 | insufficient_quota: IDR balance cannot cover an OpenAI-compatible text request — top up. Also returned when rate-limited — back off and retry. |
| 5xx | Upstream provider failure. Credits for failed async jobs are auto-refunded. |
Available models
Live catalog. See /pricing for IDR rates per tier.
| Model id | Display name |
|---|---|
deepseek-v4-flash | DeepSeek V4 Flash (Routing to v4.1) |
gemini-3.1-flash-lite | Gemini 3.1 Flash Lite |
claude-sonnet-5 | Claude Sonnet 5 |
claude-opus-5 | Claude Opus 5 |
kimi-k3 | Kimi K3 |
kimi-k3-fast | Kimi K3 Fast |
gemini-3.7-flash | Gemini 3.7 Flash |
gemini-3.5-flash-lite | Gemini 3.5 Flash Lite |
qwen-3.8-27b | Qwen3.8 27B |
glm-5.3 | GLM 5.3 |
claude-fable-5-1 | Claude Fable 5.1 |
gemini-3.8-flash | Gemini 3.8 flash |
gpt-6-astra | GPT 6 Astra |
deepseek-flash | Deepseek V4.1 Flash |
claude-opus-5-5 | Claude opus 5.5 |
gpt-6-sol | Gpt 6 sol |
gpt-6-luna | Gpt 6 luna |
claude-sonnet-5-5 | Claude Sonnet 5.5 |
deepseek-v4-pro | DeepSeek V4 Pro |
claude-haiku-4-5 | Claude Haiku 4.5 |
minimax-m3 | MiniMax M3 |
glm-5.2 | GLM 5.2 |
glm-5.2-fast | GLM 5.2 Fast |
kimi-k2.7-code | Kimi K2.7 Code |
| Model id | Display name |
|---|---|
gpt-image-2 | GPT Image 2 |
gpt-image-2.5-flare | Gpt image 2.5 flare |
gpt-image-2.5-sunburst | Gpt image 2.5 sunburst |
nano-banana2-flash | Nano Banana 2 Flash |
z-image-turbo | Z-Image Turbo |
wan-2.7-text2img | WAN 2.7 |
| Model id | Display name |
|---|---|
gpt-image-2-img2img | GPT Image 2 |
gpt-image-2.5-flare-img2img | Gpt image 2.5 flare |
gpt-image-2.5-sunburst-img2img | Gpt image 2.5 sunburst |
nano-banana2-img2img | Nano Banana 2 |
z-image-turbo-img2img | Z-Image Turbo |
wan-2.7-img2img | WAN 2.7 |
| Model id | Display name |
|---|---|
seedance-2.0-fast | Seedance 2.0 Fast |
seedance-2.0-fast-img2video | Seedance 2.0 Fast (img2video) |
seedance-2.0-fast-multimodal | Seedance 2.0 Fast (multimodal) |
txt2video-sora-2 | Sora 2 |
img2video-sora-2 | Sora 2 (Image to Video) |
minimax-h3-multimodal | Minimax H3 (Multimodal) |