Neironych for developers

Your ideas.
Our API.

Text, images and video in your product. Everything you need, from your first request to the result.

TextImagesVideo

Neironych API · Contract reviewed 2026-09-28. This reference covers generation requests, parameters and responses. Obtain a key through partner onboarding. Generations require an active key, an enabled model and sufficient balance.

Connection

https://api.xn--e1aikcel5c5a.online
Authorization: Bearer <PARTNER_API_KEY>
Content-Type: application/json
Idempotency-Key: <unique-request-key>
X-Client-Request-Id: <your-task-id>

The base URL above does not include /v1. Append the complete method path. Every operation except GET /v1/models requires authentication. x-api-key is an alternative to Authorization; do not send both. Keep the key on your server, never in a browser or mobile client.

Idempotency-Key is required for every text, image and video generation POST: a string of 8–160 characters, such as a UUID. A key identifies one request across the partner account, including its other API keys. Persist the key and original body before submitting. Status, download and upload-ticket operations do not need it.

Optional X-Client-Request-Id persists your system task UUID and returns it in status and callback payloads. For generation:<UUID>:provider:<N> keys, the UUID is extracted automatically. Recover a lost response with GET /api/v1/generations/by-client-request-id/<UUID>.

export API_BASE="https://api.xn--e1aikcel5c5a.online"
export API_KEY="<PARTNER_API_KEY>"
curl --fail-with-body "$API_BASE/v1/models"
MethodSuccess response
GET /v1/models200 application/json
GET /v1/balance200 application/json
POST /v1/responses200 application/json | text/event-stream
POST /v1/chat/completions200 application/json | text/event-stream
POST /v1/messages200 application/json | text/event-stream
POST /v1/images/generations200 application/json
POST /v1/images/edits200 application/json
POST /v1/videos/generations202 application/json
GET /v1/videos/{request_id}200 application/json
GET /v1/videos/{request_id}/content200 video/mp4 | 206 partial content
GET /api/v1/generations/{request_id}/trace200 application/json
POST /v1/media/uploads201 application/json

Partner balance

GET /v1/balance returns only the current RUB balance belonging to the provided partner API key. Use Authorization: Bearer <PARTNER_API_KEY> or x-api-key. Another partner's balance and admin API tokens cannot be used here. This GET creates no generation, charges nothing and needs no Idempotency-Key. Responses have Cache-Control: no-store.

curl --fail-with-body "$API_BASE/v1/balance" -H "Authorization: Bearer $API_KEY"
{
  "partner_id": "<your-partner-id>",
  "balance_rub": "999.99",
  "credit_limit_rub": "250.00",
  "available_balance_rub": "1249.99"
}

balance_rub is the actual RUB cash balance, credit_limit_rub the separately approved limit, and available_balance_rub their sum. For low cash alerts compare balance_rub, not credit availability. Amounts are returned as decimal strings with kopeck precision.

Model availability

GET /v1/models returns currently enabled model IDs, with no pagination or parameters. data may be empty. A model appearing in an example or limits table below does not mean it is enabled for generations. Individual modes and sizes must also be available for your connection. Send the exact model ID, including its case and version.

{
  "object": "list",
  "data": [
    {
      "id": "seedance-2.5",
      "object": "model",
      "owned_by": "neironych"
    }
  ]
}
ModelModel IDEndpoint
GLM-5.3 Flashglm-5.3-flash/v1/chat/completions
GPT-5.6 Lunagpt-5.6-luna/v1/responses
GPT Image 2gpt-image-2/v1/images/generations
GPT Image 2.5 Sunburstgpt-image-2.5-sunburst/v1/images/generations
Grok 4.5grok-4.5/v1/responses
Grok Imagine Video 1.5grok-imagine-video-1.5/v1/videos/generations
Nano Banana 2nano-banana-2/v1/images/generations
Nano Banana 2 Litenano-banana-2-lite/v1/images/generations
Nano Banana 2.1nano-banana-2.1/v1/images/generations
Nano Banana Pronano-banana-pro/v1/images/generations
Qwen3.8-Maxqwen3.8-max/v1/chat/completions
seedance-2.0seedance-2.0/v1/videos/generations
Seedance 2.5seedance-2.5/v1/videos/generations
Seedream 5.0 Proseedream-5.0-pro/v1/images/generations
Wan 3wan-3/v1/videos/generations
Wan 3 Primewan-3-prime/v1/videos/generations

Text models

Select an enabled text model from GET /v1/models. Example IDs illustrate request syntax and do not indicate current availability. Use Responses or Chat Completions for GPT models. Messages is for Claude and compatible models. Vision, tools, JSON Schema and reasoning capabilities depend on the selected model; changing the protocol does not add capabilities.

GPT-5.6 Luna

gpt-5.6-luna accepts text and images and returns text. Use POST /v1/responses with input or POST /v1/chat/completions with messages; /v1/messages is not supported for this model. Images use input_image in Responses or image_url in Chat, with public HTTPS URLs or data URLs.

Use reasoning.effort in Responses or reasoning_effort in Chat: low, medium, high, xhigh, max; the default is medium. Context is 1,050,000 tokens, input up to 922,000, output up to 128,000. Reasoning tokens count toward the output cap and are billed as output tokens.

service_tier is an optional string. Omit it for the standard tier. priority or fast selects Fast: every published RUB rate for this model is multiplied by 2, including input, output, cache reads and cache writes. This rule is for Luna; check other models separately for Fast support.

GLM-5.3 Flash

For glm-5.3-flash, use POST /v1/chat/completions. The model accepts text and image_url inputs with public HTTPS URLs or data URLs and returns text. Context is 1,000,000 tokens, output up to 128,000. Thinking is always enabled; reasoning_effort accepts high or max, default max. low and medium run as high; xhigh runs as max. service_tier is not supported: omit it.

Responses and Messages are available through conversion to Chat Completions with limitations. Responses accepts text or input_image; input_file is not carried over. Messages supports text, tool blocks and base64 images only; URL images are not carried over. Web search and other server-side tools are unsupported in both conversions. reasoning.effort in Responses uses the same levels. Set output_config.effort explicitly in Messages: when omitted, medium is sent, which this model runs as high.

All three methods accept JSON and require Authorization: Bearer, Content-Type: application/json and a unique Idempotency-Key for every new operation. In these examples API_BASE is the base URL shown above, without a trailing /v1, and API_KEY is your key. Create REQUEST_KEY once and save it with the request body. Reuse it for the same operation; create another key for a new request.

REQUEST_KEY="$(python3 -c 'import uuid; print(uuid.uuid4())')"
FieldTypeRequired / valueMeaning
modelstringYesExact text model ID.
streambooleanNo; normally falsetrue selects SSE; false requests one JSON response.
max_output_tokens / max_completion_tokens / max_tokensinteger1–10,000,000Use the field for your protocol below. This is the API validation boundary, not a model limit. Actual output and context limits may be lower. Set an explicit cap; reasoning tokens may consume it.
temperature / top_pnumberNo; model-specificSampling controls. Ranges and reasoning compatibility depend on the model; omit both for the first request.

Text model limits and capabilities

Reviewed 2026-09-28. These are declared technical capabilities, not a promise that every ID is available: an enabled model must appear in GET /v1/models. Context includes input and output together; maximum input and output may not be achievable simultaneously. System instructions, history, tools and reasoning also consume tokens. Not declared means there is no confirmed value; it does not mean zero, unlimited capacity or support for a feature. The API validates the common format; a model may reject requests that exceed its own limit.

Model IDContext tokensMax inputMax outputDefault outputVision
gpt-6-astra1 050 000922 000128 000Not declaredYes
gpt-5.6-luna1 050 000922 000128 000Not declaredYes
gpt-5.6-terra1 050 000922 000128 000Not declaredYes
gpt-5.6-sol1 050 000922 000128 000Not declaredYes
claude-opus-51 000 000Not declared128 000Not declaredYes
claude-fable-5-11 000 000Not declared128 000Not declaredYes
claude-fable-51 000 000Not declared128 000Not declaredYes
grok-4.6500 000Not declaredNot declaredNot declaredYes
claude-opus-4-81 000 000Not declared128 000Not declaredYes
claude-sonnet-51 000 000Not declared128 000Not declaredYes
deepseek-v4-flash-vision-expNot declaredNot declaredNot declaredNot declaredNot declared
deepseek-v4-pro-0813Not declaredNot declaredNot declaredNot declaredNot declared
glm-5.1200 000Not declared128 000Not declaredNot declared
glm-5.21 000 000Not declared128 000Not declaredNot declared
glm-5.31 000 000Not declared128 000Not declaredNot declared
glm-5.3-flash1 000 000Not declared128 000Not declaredYes
gpt-5.3-codex-sparkNot declaredNot declaredNot declaredNot declaredNot declared
gpt-5.41 050 000Not declared128 000Not declaredYes
gpt-5.4-mini400 000272 000128 000Not declaredYes
gpt-5.51 050 000Not declared128 000Not declaredYes
grok-4.5500 000Not declaredNot declaredNot declaredYes
kimi-k2.6262 144Not declaredNot declaredNot declaredNot declared
kimi-k2.7-code262 144Not declaredNot declaredNot declaredNot declared
kimi-k31 048 576Not declared1 048 576131 072Yes
qwen3.8-max1 000 000983 616131 072Not declaredYes

The next table separates listed levels, additional accepted values and declared defaults. An absent default does not imply medium. For GPT models with default=none, do not replace an omitted field with an arbitrary effort level. The effort field depends on the protocol, as described below. See Reasoning / thinking for additional GLM/Kimi rules.

Model IDEffort levelsAdditional values → effective valuesDefault effortThinking
gpt-6-astralow / medium / high / xhigh / maxNot declaredNot declaredNot declared
gpt-5.6-lunalow / medium / high / xhigh / maxNot declaredmediumNot declared
gpt-5.6-terralow / medium / high / xhigh / maxNot declaredmediumNot declared
gpt-5.6-sollow / medium / high / xhigh / maxNot declaredmediumNot declared
claude-opus-5low / medium / high / xhigh / maxNot declaredhighSwitchable
claude-fable-5-1low / medium / high / xhigh / maxNot declaredhighAlways enabled
claude-fable-5low / medium / high / xhigh / maxNot declaredhighAlways enabled
grok-4.6low / medium / high / xhighmax → highhighAlways enabled
claude-opus-4-8low / medium / high / xhigh / maxNot declaredhighOpt-in
claude-sonnet-5low / medium / high / xhigh / maxNot declaredhighSwitchable
deepseek-v4-flash-vision-expNot declaredNot declaredNot declaredNot declared
deepseek-v4-pro-0813Not declaredNot declaredNot declaredNot declared
glm-5.1Not declaredNot declaredNot declaredNot declared
glm-5.2high / maxlow → high; medium → high; xhigh → maxmaxNot declared
glm-5.3low / high / maxmedium → high; xhigh → maxmaxAlways enabled
glm-5.3-flashhigh / maxlow → high; medium → high; xhigh → maxmaxAlways enabled
gpt-5.3-codex-sparkNot declaredNot declaredNot declaredNot declared
gpt-5.4low / medium / high / xhighNot declarednoneNot declared
gpt-5.4-minilow / medium / high / xhighNot declarednoneNot declared
gpt-5.5low / medium / high / xhighNot declaredmediumNot declared
grok-4.5low / medium / highmax → high; xhigh → highhighAlways enabled
kimi-k2.6Not declaredNot declaredNot declaredNot declared
kimi-k2.7-codeNot declaredNot declaredNot declaredAlways enabled
kimi-k3low / high / maxNot declaredmaxAlways enabled
qwen3.8-maxNot declaredNot declaredNot declaredAlways enabled

Claude Opus 5: thinking can be disabled only at effort high or lower. Claude Fable 5.1: do not force a tool with tool_choice any/tool. Claude Sonnet 5: omit temperature, top_p and top_k; arbitrary overrides of these settings are unsupported. Current limits are unconfirmed for deepseek-v4-flash-vision-exp, deepseek-v4-pro-0813 and gpt-5.3-codex-spark; settings from other IDs do not automatically apply to them.

POST /v1/responses

FieldTypeRequired / valueMeaning
inputstring | arrayYesText or {role, content} messages. content is a string or a list of input_text/input_image blocks. Roles are typically user/assistant/system/developer.
instructionsstringNo; model-specificInstructions for this request.
max_output_tokensintegerNo; model-specificOutput token cap.
previous_response_idstringNo; model-specificid of a completed response from this API for your account. Continuation is model-specific. For invalid_previous_response, submit a new request with explicit conversation history.
tools / tool_choicearray / string | objectNo; model-specificFunctions with type=function, name and parameters; select auto/none/required or a function.
parallel_tool_callsbooleanNo; model-specificAllow multiple function calls in one response.
reasoningobjectNo; model-specificFor example {"effort":"low"}. Accepted levels depend on the model.
text.formatobjectNo; model-specificText format; json_schema uses name, schema and strict. See the example below.
store / metadataboolean / objectNo; model-specificAdditional options for models that support them; these do not promise retention or a separate response retrieval method.
curl --fail-with-body -sS -N "$API_BASE/v1/responses" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "model": "gpt-5.6-sol",
  "input": "Explain a database transaction in two sentences.",
  "max_output_tokens": 512
}'

HTTP 200: traverse output[] and collect content[] blocks with type=output_text. The array can also contain reasoning and function_call items. Do not assume a fixed text index. status=incomplete means a partial result; inspect incomplete_details.

{
  "id": "8488e6a4-d38a-4f95-a831-752aa12bf5d7",
  "object": "response",
  "status": "completed",
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "Ready."
        }
      ]
    }
  ],
  "usage": {
    "input_tokens": 12,
    "output_tokens": 3,
    "total_tokens": 15
  }
}

POST /v1/chat/completions

FieldTypeRequired / valueMeaning
messagesarrayYesOrdered conversation {role, content}: system/developer/user/assistant/tool. content is text or text/image_url blocks; supported roles depend on the model.
max_completion_tokensintegerNo; model-specificPreferred output cap field.
max_tokensintegerNo; model-specificCompatibility cap for models using the earlier format. Do not combine with max_completion_tokens.
tools / tool_choicearray / string | objectNo; model-specifictools[].type=function, with definitions in tools[].function; select auto/none/required or a named function.
parallel_tool_callsbooleanNo; model-specificAllow multiple functions per response.
response_formatobjectNo; model-specifictype=json_object for JSON, or type=json_schema with nested json_schema for a schema. Also request JSON in the prompt.
reasoning_effort / thinkingstring / objectNo; model-specificSee the model table below; these are distinct controls, not aliases.
stream_optionsobjectNo; model-specificWith stream=true the API requests include_usage=true; process the final usage chunk even when choices is empty.
stop / seed / presence_penalty / frequency_penaltystring | array / integer / number / numberNo; model-specificStop sequences, sampling seed and repetition controls only for supporting models. seed does not guarantee exact reproduction.
curl --fail-with-body -sS -N "$API_BASE/v1/chat/completions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "model": "glm-5.3",
  "messages": [
    {
      "role": "user",
      "content": "Explain a database transaction in two sentences."
    }
  ],
  "max_completion_tokens": 2048,
  "reasoning_effort": "low"
}'

HTTP 200: text is in choices[].message.content, function calls in message.tool_calls and the termination reason in finish_reason. length indicates that a cap was reached and text may be truncated.

{
  "id": "8488e6a4-d38a-4f95-a831-752aa12bf5d7",
  "object": "chat.completion",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Ready."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 3,
    "total_tokens": 15
  }
}

POST /v1/messages

Send anthropic-version: 2023-06-01. You may use x-api-key with your key when Authorization is absent. Use the optional anthropic-beta header only for a confirmed feature of the chosen model.

FieldTypeRequired / valueMeaning
messagesarrayYesuser/assistant messages; content is text or text/image/tool_use/tool_result blocks.
max_tokensintegerYesOutput cap, required even with stream=true.
systemstring | arrayNo; model-specificSeparate system instructions or text blocks. Do not use a system role inside messages.
tools / tool_choicearray / objectNo; model-specificDefinitions with name, description and input_schema; choices {type:auto}, {type:any}, {type:tool,name:...} or {type:none}, when supported by the model.
thinkingobjectNo; model-specificReasoning mode. type and budget_tokens depend on the model generation; budget_tokens does not replace required max_tokens.
output_configobjectNo; model-specificEffort and output format for supporting models. Do not copy a Chat Completions response_format object into this field.
stop_sequences / top_k / metadataarray / integer / objectNo; model-specificStop sequences, sampling cutoff and metadata; availability depends on the model.
curl --fail-with-body -sS -N "$API_BASE/v1/messages" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  --data '{
  "model": "claude-sonnet-5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": "Explain a database transaction in two sentences."
    }
  ]
}'

HTTP 200: content[] contains text, tool_use and other model blocks. Read every text block; stop_reason=max_tokens indicates capped output, while tool_use requests a tool. The usage counters are input_tokens and output_tokens.

{
  "id": "8488e6a4-d38a-4f95-a831-752aa12bf5d7",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "Ready."
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 12,
    "output_tokens": 3
  }
}

Tools and JSON Schema

The bodies below use the corresponding endpoints and the same headers. Your application executes functions after checking arguments and permissions. Send the result in a subsequent request with a new Idempotency-Key. Responses uses an input item with type=function_call_output, call_id and string output. Chat uses a role=tool message with tool_call_id and string content after the assistant tool_calls message. Messages uses a user tool_result block with tool_use_id and content after the assistant tool_use block. Preserve call identifiers and conversation history.

responses — tools

{
  "model": "gpt-5.6-sol",
  "input": "Check order A123 using the available tool.",
  "max_output_tokens": 512,
  "tools": [
    {
      "type": "function",
      "name": "get_order",
      "description": "Read an order status.",
      "parameters": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string"
          }
        },
        "required": [
          "order_id"
        ],
        "additionalProperties": false
      },
      "strict": true
    }
  ],
  "tool_choice": "auto"
}

chat/completions — tools

{
  "model": "gpt-5.6-sol",
  "messages": [
    {
      "role": "user",
      "content": "Check order A123 using the available tool."
    }
  ],
  "max_completion_tokens": 512,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_order",
        "description": "Read an order status.",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            }
          },
          "required": [
            "order_id"
          ],
          "additionalProperties": false
        }
      }
    }
  ],
  "tool_choice": "auto"
}

messages — tools

{
  "model": "claude-sonnet-5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": "Check order A123 using the available tool."
    }
  ],
  "tools": [
    {
      "name": "get_order",
      "description": "Read an order status.",
      "input_schema": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string"
          }
        },
        "required": [
          "order_id"
        ]
      }
    }
  ],
  "tool_choice": {
    "type": "auto"
  }
}

For a strict schema, list required properties in required; additionalProperties=false excludes extra keys. Validate returned JSON in your application and handle model refusals and token caps. Acceptance of a field alone does not establish structured-output support.

responses — JSON Schema

{
  "model": "gpt-5.6-sol",
  "input": "Return the word ready in a status field.",
  "max_output_tokens": 512,
  "text": {
    "format": {
      "type": "json_schema",
      "name": "status_result",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false
      }
    }
  }
}

chat/completions — JSON Schema

{
  "model": "gpt-5.6-sol",
  "messages": [
    {
      "role": "user",
      "content": "Return the word ready in a status field."
    }
  ],
  "max_completion_tokens": 512,
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "status_result",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false
      }
    }
  }
}

Image input for text models

Choose a vision-capable model. Replace https://example.com/reference.jpg with a directly accessible HTTPS image URL. Base64 support is model-specific: Responses input_image.image_url and Chat image_url.url use data:image/jpeg;base64,...; Messages uses a source with type=base64, media_type=image/jpeg and raw base64 data. For Kimi K3, use base64 data URLs. Image size/count limits and file or audio input support are not shared across all text models.

responses — vision

{
  "model": "gpt-5.6-sol",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "Describe the image."
        },
        {
          "type": "input_image",
          "image_url": "https://example.com/reference.jpg"
        }
      ]
    }
  ],
  "max_output_tokens": 512
}

chat/completions — vision

{
  "model": "gpt-5.6-sol",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Describe the image."
        },
        {
          "type": "image_url",
          "image_url": {
            "url": "https://example.com/reference.jpg"
          }
        }
      ]
    }
  ],
  "max_completion_tokens": 512
}

messages — vision

{
  "model": "claude-sonnet-5",
  "max_tokens": 512,
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Describe the image."
        },
        {
          "type": "image",
          "source": {
            "type": "url",
            "url": "https://example.com/reference.jpg"
          }
        }
      ]
    }
  ]
}

Video input in Qwen3.8-Max

The original Qwen3.8-Max model can understand videos, but the current Neironych route supports text and image input only. The video_url, input_video and video parts return HTTP 422 invalid_request_contract with X-Validation-Error: video_input_unavailable before task creation or charging. Do not silently replace video with still frames. Text requests require an Idempotency-Key of 8 to 160 characters.

Reasoning / thinking

For GLM and Kimi, use reasoning_effort through Chat Completions. The table applies when the corresponding model is available; levels are not universal. GPT uses reasoning.effort in Responses or reasoning_effort in Chat. Claude thinking and output_config depend on generation; omit them unless the configuration is confirmed. HTTP 200 does not prove that an arbitrary unsupported setting affected the result.

FieldTypeRequired / valueMeaning
glm-5.1reasoning_effortnoneEnabled by default; none disables it.
glm-5.2reasoning_effortnone / low / medium / high / xhigh / maxDefault max; low/medium/high → high, xhigh/max → max; none disables it.
glm-5.3reasoning_effortlow / high / maxDefault max; cannot be disabled.
glm-5.3-flashreasoning_efforthigh / maxDefault max; low/medium → high, xhigh → max; always enabled.
kimi-k2.6reasoning_effortlow / medium / high / xhighDisabled when omitted; these values enable it. max is unsupported.
kimi-k2.7-code——Always enabled; thinking/reasoning_effort do not set a level.
kimi-k3reasoning_effortlow / high / maxDefault max; always enabled. Keep one level throughout a conversation.
curl --fail-with-body -sS -N "$API_BASE/v1/chat/completions" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -H "Content-Type: application/json" \
  --data '{
  "model": "kimi-k3",
  "messages": [
    {
      "role": "user",
      "content": "List three checks before saving a payment."
    }
  ],
  "max_completion_tokens": 2048,
  "reasoning_effort": "low",
  "stream": true
}'

SSE / stream=true

A successful stream has Content-Type: text/event-stream and X-Request-Id. Events end with a blank line; network chunk boundaries are not event boundaries. Collect data: lines to the end of an event before parsing JSON. Handle event: names, control blocks and errors; do not parse [DONE] as JSON. The shortened examples below use illustrative text and token counts.

event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"Ready."}

event: response.completed
data: {"type":"response.completed","response":{"id":"8488e6a4-d38a-4f95-a831-752aa12bf5d7","status":"completed","usage":{"input_tokens":12,"output_tokens":3}}}

Responses: accumulate response.output_text.delta.delta; completion events are response.completed or response.incomplete. The latter is partial output. Function-call arguments arrive in separate events and must also be assembled before JSON parsing.

data: {"choices":[{"index":0,"delta":{"content":"Ready."},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"choices":[],"usage":{"prompt_tokens":12,"completion_tokens":3,"total_tokens":15}}

data: [DONE]

Chat: accumulate choices[].delta by index; content and tool_calls may be partial. Do not stop reading at finish_reason: a usage chunk with empty choices may follow.

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Ready."}}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":3}}

event: message_stop
data: {"type":"message_stop"}

Messages: handle message_start, content_block_start/delta/stop, message_delta and message_stop. text_delta.text contains text; input_json_delta.partial_json contains partial tool arguments. Usage counters may arrive in multiple events.

Save X-Request-Id for support. A broken stream or client timeout does not cancel the request; partial text does not establish success. Do not automatically create a new paid operation after an interruption. Repeating a synchronous request with the same key returns 409 request_already_submitted rather than replaying the text. See the shared error reference.

Images

POST /v1/images/generations creates images. POST /v1/images/edits accepts JSON with images or multipart/form-data with files. Requests are synchronous: wait for the response; a delay is not a reason to submit another generation.

ParameterType / requiredRules
modelstring · requiredImage model ID from /v1/models.
promptstring · requiredNon-empty output/edit description.
ninteger · optional, 1GPT Image: 1–7; Nano Banana: 1–4; Grok Image: 1–10; Seedream 5.0 Pro: 1.
imagesarray<object> · editsJSON: [{"image_url":"https://…"}] or a data:image/…;base64,… image_url; GPT up to 16, Nano Banana Pro up to 14, Seedream 5.0 Pro up to 8, others up to 3.
maskobject | file · optionalGPT edits: {"image_url":"https://…/mask.png"} or a PNG file with alpha; transparent areas are edited. Match the first image dimensions; at most one mask.
sizestring · optional, autoGPT: auto or WIDTHxHEIGHT, e.g. 1024x1024. Both dimensions are positive. Actual output pixels may differ; inspect the result.
qualitystring · optional, autoGPT Image: auto | low | medium | high.
resolutionstring · optionalNano Banana 2/Pro: 1k (default), 2k or 4k. Grok: 1k or 2k; availability depends on model/mode. Nano Banana 2 Lite supports only 1k. GPT, including Sunburst: set dimensions using size, e.g. 3840x2160 for 4K.
aspect_ratiostring · optionalNano: 1:1 (default), 16:9, 9:16, 4:3, 3:4. Nano Banana Pro also accepts 3:2, 2:3, 5:4, 4:5, 21:9; edits without aspect_ratio also default to 1:1 rather than following the reference frame. For GPT, also describe the desired composition in prompt.
response_formatstring · optionalb64_json or url for GPT/Grok; Nano supports only b64_json. Nano Banana Pro returns JPEG in data[].b64_json. Set the format explicitly for predictable output.
POST /v1/images/generations
{
  "model": "gpt-image-2",
  "prompt": "A ceramic cup on a plain blue background",
  "n": 1,
  "size": "1024x1024",
  "response_format": "b64_json"
}
POST /v1/images/edits
{
  "model": "gpt-image-2",
  "prompt": "Change the background to blue",
  "images": [
    {
      "image_url": "https://media.example.com/cup.png"
    }
  ],
  "n": 1,
  "response_format": "b64_json"
}

Nano Banana 2 · 4K

POST /v1/images/generations
{
  "model": "nano-banana-2",
  "prompt": "A cinematic mountain panorama",
  "resolution": "4k",
  "aspect_ratio": "16:9",
  "n": 1,
  "response_format": "b64_json"
}

Nano Banana Pro · 4K

POST /v1/images/generations
{
  "model": "nano-banana-pro",
  "prompt": "A cinematic mountain panorama",
  "resolution": "4k",
  "aspect_ratio": "21:9",
  "n": 1,
  "response_format": "b64_json"
}

Seedream 5.0 Pro

POST /v1/images/generations
{
  "model": "seedream-5.0-pro",
  "prompt": "A ceramic cup on a plain background",
  "resolution": "2k",
  "aspect_ratio": "16:9",
  "n": 1,
  "response_format": "b64_json"
}
POST /v1/images/edits
{
  "model": "seedream-5.0-pro",
  "prompt": "Change the cup color to green",
  "images": [
    {
      "image_url": "https://media.example.com/cup.png"
    }
  ],
  "resolution": "2k",
  "n": 1
}

Seedream 5.0 Pro: 1k or 2k (default), n=1 only; JPEG. Aspect ratios: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 21:9, 2:1, 1:2, 9:21. Edits accept up to 8 JSON images or multipart image[] references, 48 MiB combined. The provider validates the size of HTTPS references. Response format: b64_json or url. size, quality and stream are unsupported. Check /v1/models for availability.

GPT Image 2.5 Sunburst · 4K

POST /v1/images/generations
{
  "model": "gpt-image-2.5-sunburst",
  "prompt": "A cinematic mountain panorama",
  "size": "3840x2160",
  "quality": "high",
  "n": 1,
  "response_format": "b64_json"
}

GPT Image 2.6 Flare / Sunburst

POST /v1/images/generations
{
  "model": "gpt-image-2.6-flare",
  "prompt": "A ceramic cup on a plain blue background",
  "size": "2048x1152",
  "quality": "max",
  "n": 1,
  "response_format": "b64_json"
}
POST /v1/images/edits
{
  "model": "gpt-image-2.6-sunburst",
  "prompt": "Change the cup color to green",
  "images": [
    {
      "image_url": "https://media.example.com/cup.png"
    }
  ],
  "size": "1440x1440",
  "quality": "xhigh",
  "n": 1,
  "response_format": "b64_json"
}

Both variants support 1K and 2K: size=auto or widthxheight, with a longest edge of 2048. quality: auto, low, medium, high, xhigh, max; n from 1 to 7. Edits accept up to 16 references via JSON images or multipart image[]. Response format: b64_json or url. resolution, aspect_ratio and 4K are unsupported. Each returned image is billed; check /v1/models for availability.

For multipart, send model, prompt and n as form fields; repeat image[] for files (image, images and images[] are also accepted). File limits: GPT 16, Nano Banana Pro 14, other Nano 3, Grok 1, Seedream 5.0 Pro 8; plus one GPT mask. Do not set Content-Type manually: the client supplies the boundary.

curl --fail-with-body "$API_BASE/v1/images/edits" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -F "model=gpt-image-2" -F "prompt=Change the background to blue" \
  -F "n=1" -F "response_format=b64_json" -F "image[]=@reference.png"
{
  "created": 1790596800,
  "data": [
    {
      "b64_json": "<base64-encoded-image>"
    }
  ]
}

The response contains data[] with b64_json or url in the chosen format, and may include revised_prompt. Decode base64 or download the URL promptly. Links are temporary; permanent retention is not guaranteed. Never send your API key to external image URLs. X-Request-Id contains our request ID. Recover a lost HTTP response through status lookup by our ID or X-Client-Request-Id. GPT size tiers use actual pixels: longest edge up to 1024 is 1K, up to 2048 is 2K, above is 4K. Each returned image counts.

Video: creation and retrieval

POST /v1/videos/generations accepts JSON only. Every model returns 202 with request_id; this acknowledges the request, not a completed video. Save the ID. duration values are integer seconds.

ParameterType / requiredRules
modelstring · requiredExact enabled video model ID.
promptstringRequired without input media; always required for edit, Kling, Wan and MiniMax. Seedance: up to 40,000 UTF-8 bytes; Wan: up to 4,500 characters. Refer to inputs as @Image 1, @Video 1, @Audio 1.
durationintegerSet explicitly within the model range below. Seedance/Kling/MiniMax/Wan default to 5 when omitted; always set it for Grok to make length predictable. Seedance 2.5 edit: -1 or omit.
resolutionstring · optionalDefault 720p, or 768p for MiniMax. Allowed case-sensitive values are listed below.
aspect_ratiostring · optionalSet explicitly for text and reference modes. Base values: 1:1, 16:9, 9:16, 4:3, 3:4. Seedance/MiniMax also support 21:9; Grok also supports 3:2, 2:3. See adaptive rules below.
ninteger · optional, 1Only 1.
reference_images / reference_videos / reference_audiosarray<object> · optional[{"url":"https://…"}]. Order determines reference numbering. Per-type and combined limits below. Do not supply a reference duration: it is determined from the file.
start_image / end_imageobject · optionalSeedance/MiniMax/Wan: {"url":"https://…"}. end_image requires start_image. Frames cannot be combined with reference_*.
imageobject · optionalGrok: first frame {"url":"https://…"}; do not combine with reference_images.
omni_reference_task_typestring · optional, autoSeedance 2.5: auto, reference, edit or extend; see the dedicated edit/extend examples.
draft / draft_taskboolean / object · Seedance 2.5draft=true creates a 480p preview. For the full 1080p result send only model and draft_task:{"id":"11111111-1111-4111-8111-111111111111"} within 7 days.
generate_audioboolean · optionalSeedance 2.5 and Kling 4.0: true generates audio, false returns a silent video; use a boolean, not a string. Wan also supports an audio toggle. Other model contracts are unchanged.
seed / watermarkmodel-dependentSeedance/Kling/MiniMax: seed is unsupported; watermark=true is rejected. Passing format validation for another model does not establish support for an option.
Model IDSecondsresolutionImages / videos / audio / total
seedance-2.54–30480p, 720p, 1080p30 / 10 / 10 / 50
seedance-2.04–15480p, 720p, 1080p, 4k9 / 3 / 3 / 12
grok-imagine-video-1.51–15480p, 720p, 1080p7 / 0 / 0 / 7

Self-Developed NSFW: use the exact Model ID in the table. Frame, reference and audio rules follow the Seedance 2.0 or 2.5 family; version 2.5 also supports edit. Self-Developed NSFW variants reject 480p: 2.0 supports 720p, 1080p and 4k; 2.5 supports 720p and 1080p. The 480p example below applies to ordinary seedance-2.5. Check GET /v1/models for current availability.

Seedance 2.5 text and ordinary references use a fixed aspect_ratio; adaptive is rejected in these modes. With start_image/end_image, use adaptive or omit aspect_ratio. Seedance 2.0 frames also accept fixed ratios. MiniMax frames: omit aspect_ratio, which is derived from the image; reference mode also accepts adaptive. Grok reference_images support only 480p/720p. Seedance 2.0 and MiniMax require image/video alongside audio; Seedance 2.5 allows audio alone.

Wan: omit aspect_ratio with input frames. Total reference_videos duration is at most 15 seconds, and output plus input videos must not exceed 30 seconds. For example, 15 seconds of reference video leaves at most 15 seconds for output. Request format validation does not replace media validation.

Seedance 2.5 · reference

POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "prompt": "Animate the image in @Image 1 with a gentle camera move.",
  "reference_images": [
    {
      "url": "https://media.example.com/reference.jpg"
    }
  ],
  "duration": 4,
  "resolution": "480p",
  "aspect_ratio": "9:16"
}

Save the JSON as request.json. Create REQUEST_KEY once and preserve it with the file; do not generate a new key when repeating the same operation.

REQUEST_KEY="$(python3 -c 'import uuid; print(uuid.uuid4())')"
curl --fail-with-body "$API_BASE/v1/videos/generations" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $REQUEST_KEY" --data-binary @request.json

Seedance 2.5 · first and last frames

POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "prompt": "A smooth camera move between the frames",
  "start_image": {
    "url": "https://media.example.com/start.jpg"
  },
  "end_image": {
    "url": "https://media.example.com/end.jpg"
  },
  "duration": 4,
  "resolution": "720p",
  "aspect_ratio": "adaptive"
}

Seedance 2.5 · edit

POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "prompt": "Replace the sky in @Video 1 with a sunset",
  "reference_videos": [
    {
      "url": "https://media.example.com/source.mp4"
    }
  ],
  "omni_reference_task_type": "edit",
  "duration": -1,
  "resolution": "720p",
  "aspect_ratio": "adaptive"
}

Seedance 2.5 · extend

POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "prompt": "Continue @Video 1 forward as the camera rises above the forest",
  "reference_videos": [
    {
      "url": "https://media.example.com/source.mp4"
    }
  ],
  "omni_reference_task_type": "extend",
  "duration": 5,
  "resolution": "720p"
}

Seedance 2.5 · draft

POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "prompt": "Preview a slow aerial shot over a foggy forest",
  "duration": 5,
  "draft": true
}
POST /v1/videos/generations
{
  "model": "seedance-2.5",
  "draft_task": {
    "id": "11111111-1111-4111-8111-111111111111"
  }
}

Seedance 2.5 billing: an ordinary reference request with reference_videos uses the edit retail rate at the selected resolution, even when combined with image/audio. Extend and draft use separate cost rates and must be covered by the active retail rate. Without reference_videos, the normal rate applies. With video input, billable seconds include output plus reference video duration; final billing uses actual provider usage. Pricing never changes the provider request mode.

seedance-2.5 edit requires prompt and exactly one reference_videos entry, 4–30 seconds long. Up to 30 reference_images may steer the edit; audio and a second video are rejected. Use reference for other mixed assets: the server never silently changes modes. Edit output duration and aspect follow that video: duration=-1 or omit, aspect_ratio=adaptive or omit; do not use start_image, end_image or fixed size.

Field aliases

The following aliases apply to Seedance, MiniMax and Wan; use the primary Grok fields for Grok.

FieldEquivalent
secondsduration
ratioaspect_ratio
image_urls: ["https://…"]reference_images: [{"url":"https://…"}]
video_urls: ["https://…"]reference_videos: [{"url":"https://…"}]
audio_urls: ["https://…"]reference_audios: [{"url":"https://…"}]
image_url: "https://…"start_image: {"url":"https://…"}
end_image_url: "https://…"end_image: {"url":"https://…"}
frame_images: [{"frame_type":"first_frame","url":"https://…"}, {"frame_type":"last_frame","url":"https://…"}]start_image + end_image
input_references: [{"type":"image_url","image_url":{"url":"https://…"}}]reference_images; type=video_url/audio_url → reference_videos/reference_audios

Choose one notation: do not duplicate a field and its alias. size=WIDTHxHEIGHT is an alternative to resolution/aspect_ratio: ratio must match 1:1, 16:9, 9:16, 4:3, 3:4 or 21:9 within 3%. Shortest edge <720 maps to 480p, <1080 to 720p, <2160 to 1080p, otherwise 4k. Explicit resolution/ratio must agree with size. MiniMax maps 1366x768 to 768p; other sizes require an explicit resolution. Prefer the primary fields above for new integrations.

Response and status polling

{
  "request_id": "<our-request-id>",
  "client_request_id": "<your-task-id>"
}
curl --fail-with-body "$API_BASE/v1/videos/$REQUEST_ID" \
  -H "Authorization: Bearer $API_KEY"
statusMeaning
pendingQueued or running; continue polling every 10–15 seconds.
doneComplete: video.url is temporarily available without auth; authenticated_url requires the key.
failedTerminal failure; inspect error if present.
expiredTask execution timed out; terminal status.
{
  "request_id": "<our-request-id>",
  "client_request_id": "<your-task-id>",
  "status": "done",
  "video": {
    "url": "https://api.xn--e1aikcel5c5a.online/api/v1/media/results/<request-id>/<expires>/<signature>",
    "expires_at": 1791201600,
    "authenticated_url": "https://api.xn--e1aikcel5c5a.online/v1/videos/<our-request-id>/content"
  },
  "usage": {
    "billed_seconds": 4
  }
}

The request_id path value comes from the 202 response and is accessible only to its partner. video appears when done; usage.billed_seconds is optional and includes chargeable input-video time, not just output duration. This API does not return progress, ETA or video.duration. Do not create a new generation because your application's waiting period elapsed.

curl --fail-with-body "$API_BASE/v1/videos/$REQUEST_ID/content" \
  -H "Authorization: Bearer $API_KEY" --output result.mp4

The stable authenticated_url requires the same partner account; temporary video.url can be sent to Telegram or a customer without exposing the API key. Range: bytes=0-1023 is supported, with 206 and Content-Range for partial responses. The file is unavailable before completion. Save the MP4 yourself: permanent retention is not guaranteed.

wan-3

2–30 seconds; 480p / 720p / 1080p; up to 10 images, 5 videos, 5 audios, 20 total. Required prompt: at most 20000 characters. Frames and edit are supported. In edit omit duration, aspect_ratio and size: the first video determines dimensions. Seed 0–4294967295 and generate_audio: false are supported.

POST /v1/videos/generations
{
  "model": "wan-3",
  "prompt": "A slow camera move around a ceramic cup",
  "duration": 5,
  "resolution": "720p",
  "generate_audio": false,
  "seed": 42
}
POST /v1/videos/generations
{
  "model": "wan-3",
  "prompt": "Make the colors in @Video 1 warmer",
  "omni_reference_task_type": "edit",
  "resolution": "720p",
  "reference_videos": [
    {
      "url": "https://media.example.com/source.mp4"
    }
  ]
}

References are HTTPS URLs. No upfront hold is placed on partner funds. Confirmed usage.billed_seconds determines the final charge. Images and audio have no extra fee. One request produces one video. Save request_id from HTTP 202, poll GET /v1/videos/{request_id}; on done download /v1/videos/{request_id}/content. Do not replay a slow POST with a new key.

Input clips: 1–15 seconds, 15 total. Output and video references together: at most 30 seconds.

wan-3-prime

4–30 seconds; 480p / 720p / 1080p; up to 10 images, 5 videos, 5 audios, 20 total. Required prompt: at most 20000 characters. Ratios: 16:9, 1:1, 9:16. No frames, edit, seed, adaptive ratio or silent output. Audio references need an image or video.

POST /v1/videos/generations
{
  "model": "wan-3-prime",
  "prompt": "Animate the subject in @Image 1",
  "duration": 5,
  "resolution": "720p",
  "aspect_ratio": "16:9",
  "reference_images": [
    {
      "url": "https://media.example.com/reference.jpg"
    }
  ]
}

References are HTTPS URLs. No upfront hold is placed on partner funds. Confirmed usage.billed_seconds determines the final charge. Images and audio have no extra fee. One request produces one video. Save request_id from HTTP 202, poll GET /v1/videos/{request_id}; on done download /v1/videos/{request_id}/content. Do not replay a slow POST with a new key.

Input clips: 1–15 seconds, 15 total. Output and video references together: at most 30 seconds.

Uploading reference media

For a local file, create an upload ticket, then send bytes to upload_url. Use media_url in the generation request. Upload and generation are separate operations. POST /v1/media/uploads accepts JSON file metadata, not the file itself. Do not send multipart/form-data, raw JPEG/PNG bytes or base64 instead of JSON. An unsupported Content-Type returns 415 media_upload_requires_json; malformed JSON returns 422 invalid_request_contract.

POST /v1/media/uploads
Content-Type: application/json
Authorization: Bearer <PARTNER_API_KEY>
{
  "model": "seedance-2.5",
  "type": "image",
  "content_type": "image/jpeg",
  "size_bytes": 123456
}
FieldTypeRequired
modelstring · Model IDyes
typeimage | video | audioyes
content_typestring · MIMEyes
size_bytesinteger · actual file bytes > 0yes
{
  "upload_url": "https://storage.example.com/signed-put-url",
  "media_url": "https://storage.example.com/signed-read-url",
  "upload_expires_at": "2026-09-28T12:15:00Z",
  "expires_at": "2026-10-05T12:00:00Z"
}

201 returns signed URLs and UTC expiration timestamps. Complete PUT before upload_expires_at (normally 15 minutes); media_url works until expires_at (normally 7 days). Use the returned timestamps. Send raw bytes and exact Content-Type and Content-Length; not JSON or multipart. upload_url already contains authorization: never send your API key to storage. Preserve signed URL query parameters. Replace the example size_bytes=123456 with the actual byte length.

curl --fail-with-body -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" --upload-file reference.jpg
typecontent_typeUpload limit
imageimage/jpeg, image/png, image/webp20 MiB
videovideo/mp4, video/quicktime, video/webm500 MiB
audioaudio/mpeg, audio/wav, audio/mp4, audio/aac20 MiB

Seedance input file requirements

A successful upload does not establish model compatibility. Seedance/MiniMax require HTTPS URLs without embedded credentials, accessible throughout execution; do not embed base64 in their JSON. Their JSON body must be smaller than 1 MiB. For your own storage, use a publicly reachable HTTPS URL. The following stricter requirements apply specifically to Seedance.

InputSeedance 2.5Seedance 2.0 / Mini / Fast
ImageJPEG/PNG; ≤20 MiB; 300–6000 px/side; ratio 0.4–2.5JPEG/PNG; ≤20 MiB
VideoMP4/MOV; 2–30 s/file, ≤30 s total; ≤100 MiB/file; 24–60 fps; 300–6000 px/side; 409600–8295044 pixels; ratio 0.4–2.5MP4/MOV; 2–15 s/file, ≤15 s total; ≤50 MB/file; 200–2160 px/side
AudioWAV/MP3; 2–30 s/file, ≤30 s total; ≤15 MB/fileWAV/MP3; 2–15 s/file, ≤15 s total; ≤15 MB/file

Complete example: local photo → Seedance → MP4

Install Python 3.12+ and httpx (pip install httpx), save the code below as seedance_reference.py. Set API_BASE, API_KEY and REFERENCE_FILE to a JPEG/PNG path. Seedance 2.5 and the selected resolution must be enabled. After interruption, run again with the same STATE_FILE: it preserves the request key, body and ID. Use a different STATE_FILE for a new paid generation. Do not run two processes with one state file.

export REFERENCE_FILE="/path/to/reference.jpg"
export STATE_FILE="seedance-request.json"
python seedance_reference.py
seedance_reference.py
"""pip install httpx; set API_BASE, API_KEY, REFERENCE_FILE (JPEG or PNG).

Resume the same request by running again with the same STATE_FILE.
Use a different STATE_FILE only for an intentionally NEW paid generation.
State contains temporary media links: keep it private, never commit it.
"""

import json
import os
import time
import uuid
from pathlib import Path

import httpx


def save_state(path, state):
    temporary = path.with_suffix(path.suffix + ".tmp")
    fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w") as stream:
        json.dump(state, stream)
    temporary.replace(path)


def run():
    base = os.environ["API_BASE"].rstrip("/")  # Origin only, without /v1.
    if not base.startswith("https://"):
        raise ValueError("API_BASE must use HTTPS")
    headers = {"Authorization": "Bearer " + os.environ["API_KEY"]}
    state_path = Path(os.environ.get("STATE_FILE", "seedance-request.json"))
    output = Path(os.environ.get("OUTPUT_FILE", "result.mp4"))
    # Do not enable automatic retries of paid POST requests.
    with httpx.Client(timeout=120, follow_redirects=False) as client:
        if state_path.exists():
            state = json.loads(state_path.read_text())
            if state["api_base"] != base:
                raise ValueError("STATE_FILE belongs to a different API_BASE")
        else:
            source = Path(os.environ["REFERENCE_FILE"])
            mime = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png"}[source.suffix.lower()]
            ticket = client.post(
                base + "/v1/media/uploads",
                headers=headers,
                json={
                    "model": "seedance-2.5",
                    "type": "image",
                    "content_type": mime,
                    "size_bytes": source.stat().st_size,
                },
            )
            ticket.raise_for_status()
            media = ticket.json()
            # Signed storage URL: NEVER attach the partner API key here.
            if not media["upload_url"].startswith("https://"):
                raise ValueError("Upload URL must use HTTPS")
            with source.open("rb") as stream:
                upload = client.put(
                    media["upload_url"],
                    content=stream,
                    headers={"Content-Type": mime, "Content-Length": str(source.stat().st_size)},
                )
            upload.raise_for_status()
            state = {
                "api_base": base,
                "idempotency_key": str(uuid.uuid4()),
                "body": {
                    "model": "seedance-2.5",
                    "prompt": "Animate the image in @Image 1 with a gentle camera move.",
                    "reference_images": [{"url": media["media_url"]}],
                    "duration": 4,
                    "resolution": "480p",
                    "aspect_ratio": "9:16",
                },
            }
            # Persist the SAME key and body BEFORE submitting the paid request.
            save_state(state_path, state)
        if "request_id" not in state:
            submitted = client.post(
                base + "/v1/videos/generations",
                headers={**headers, "Idempotency-Key": state["idempotency_key"]},
                json=state["body"],
            )
            submitted.raise_for_status()
            state["request_id"] = submitted.json()["request_id"]
            save_state(state_path, state)
        print("request_id:", state["request_id"])
        status_url = base + "/v1/videos/" + state["request_id"]
        deadline = time.monotonic() + 1800
        while time.monotonic() < deadline:
            response = client.get(status_url, headers=headers)
            response.raise_for_status()
            result = response.json()
            if result["status"] in ("failed", "expired"):
                raise RuntimeError(result.get("error", result["status"]))
            if result["status"] == "done":
                # Construct our own URL; never send credentials to a returned external URL.
                with client.stream("GET", status_url + "/content", headers=headers) as download:
                    download.raise_for_status()
                    temporary = output.with_suffix(output.suffix + ".part")
                    with temporary.open("wb") as stream:
                        for chunk in download.iter_bytes():
                            stream.write(chunk)
                    temporary.replace(output)
                print("Saved:", output)
                return
            time.sleep(10)
        raise TimeoutError("Still pending. Run again with the SAME STATE_FILE to resume.")


if __name__ == "__main__":
    run()

Errors and repeated requests

Check HTTP status before processing results. Errors use either format below; detail may also be an array of request validation errors. Keep request_id or X-Request-Id when contacting support.

{
  "detail": "invalid_request_contract"
}

For 422 invalid_request_contract, X-Validation-Error may contain a safe reason code: edit_requires_single_video, invalid_duration, invalid_generate_audio or unsupported_aspect_ratio. The response body is unchanged; unknown internal errors are not disclosed. Rejection at request_validation happens before balance reservation or provider submission.

{
  "error": {
    "type": "request_already_submitted",
    "message": "request_already_submitted"
  },
  "request_id": "<our-request-id>"
}
HTTPcodeAction
401api_key_required / invalid_api_keyCheck key and account status.
402insufficient_balanceInsufficient available balance.
403partner_not_activeContact support for account activation.
404model_not_available / generation_not_found / content_not_availableCheck model ID, request_id and file readiness. Other accounts' requests are not accessible.
409idempotency_conflictThe key was used with a different body or method.
409request_already_submittedThe synchronous request is still running or predates durable response recovery.
409capability_mismatch / media_asset_not_readyMode/size unavailable, or file not ready.
410idempotent_result_expiredResponse retention expired. The old key never starts a new paid generation.
415multipart_not_supported_for_protocolMultipart is supported only for images/edits.
415media_upload_requires_jsonCreate an upload ticket using application/json file metadata. Then send the bytes with a separate PUT to upload_url.
422invalid_request_contract / invalid_idempotency_key / invalid_previous_responseCheck JSON syntax, parameters, field combinations or key length.
422 / 503upload_rejected / provider_rejected_requestModel, file or request rejected. Check the selected model's requirements.
422unknown_model_contractUnknown model ID.
429provider_rate_limitedRequest limit reached; respect Retry-After when present.
503provider_temporarily_unavailable / upload_unavailableService temporarily unavailable. Preserve the ID and check request state.
503provider_response_invalid / unexpected_provider_stream / request_deadline_exceededRequest closed without a result or charge. Reuse the original key to recover its response; contact support if needed.

For video, resubmit the same body with the same Idempotency-Key to get the original request_id. If you have the ID, simply GET its status. Completed new text/image requests replay the original response for the same key; retrieve saved image status by our ID or X-Client-Request-Id. A new key means a new operation and may incur another charge. After a timeout, network interruption or 5xx, do not change the key for automatic resubmission. Disable automatic paid POST retries in clients. After a definitive rejection, submit a corrected new request with a new key. A fixed universal RPS quota is not guaranteed here.