Official SDK quick start
The official SDKs wrap the whole flow (create → poll → download) with automatic idempotency keys and typed errors. The Node SDK has no dependencies; the Python SDK depends only on httpx. They are distributed as GitHub repositories for now; npm and PyPI releases will be announced separately.
Node
npm install github:openaiav/sdk-node # npm release to be announced
import { OpenAiav } from '@openaiav/sdk'
const client = new OpenAiav({ apiKey: process.env.OPENAIAV_API_KEY })
const task = await client.generations.createAndWait({
model: 'seedance-2.5-oa', prompt: 'She walks into the sunlight.',
resolution: '480p', aspectRatio: 'adaptive', durationSeconds: 4,
generateAudio: false
})
await client.generations.downloadToFile(task.id, './out.mp4')Python
pip install git+https://github.com/openaiav/sdk-python # PyPI release to be announced
from openaiav import OpenAiav
client = OpenAiav(os.environ["OPENAIAV_API_KEY"])
task = client.generations.create_and_wait({
"model": "seedance-2.5-oa", "prompt": "She walks into the sunlight.",
"resolution": "480p", "aspectRatio": "adaptive", "durationSeconds": 4,
"generateAudio": False,
})
client.generations.download_to_file(task["id"], "./out.mp4")Integrate in four steps
- Use the server-side
OPENAIAV_API_KEYenvironment variable to call https://api.openaiav.com/v1/models and read current model keys, pricing and reference_modes. - POST prompt, referenceMode and referenceImages to /v1/generations. Reference images are public HTTPS URLs and need no pre-registration. Success is HTTP 201.
- Save the returned id and poll the same GET /v1/generations/{id} after poll_after_ms. Keep waiting for processing, result_pending and submission_unknown; do not create another task.
- After status becomes succeeded, download with GET /v1/generations/{id}/content. A full response is HTTP 200 and a single byte range is 206.
Authentication and idempotency
export OPENAIAV_API_KEY='oav_...'
# One Idempotency-Key per create intent: every run of this script makes a new one.
# Reuse it only when retrying this same create. No uuidgen? IDEMPOTENCY_KEY=$(python3 -c 'import uuid;print(uuid.uuid4())')
IDEMPOTENCY_KEY=$(uuidgen | tr 'A-Z' 'a-z')
echo "Idempotency-Key: $IDEMPOTENCY_KEY (reuse this value only when retrying this same create)"Send Authorization: Bearer oav_... on every request and keep the key server-side. Creation needs generation:create; polling and download need generation:read. Generate a new Idempotency-Key (a UUID) for each create intent, so every run of a sample gets a new key; reuse it only when retrying that same create after an uncertain network result, and never pair one key with different bodies.
Request headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| Authorization | string | Required | Every /v1 request uses Bearer oav_...; keep the key on your server. |
| Idempotency-Key | string | Required for task creation | Generate a new value (for example a UUID) for each create intent and reuse it only when retrying that same create; the same value and body do not create or charge twice. |
| Content-Type | string | Required for POST | Generation creation uses application/json. |
Endpoints
| Method | Path | Success | Scope | Description |
|---|---|---|---|---|
| GET | /v1/models | 200 | Any valid API key | Read current sellable models, pricing, resolutions, durations and reference_modes. Validate capabilities against the catalog before submission. |
| POST | /v1/generation-quotes | 201 | generation:create | Preview the current price without locking it. Creation reprices the request and charges exactly the reserved_usd in its response as a fixed amount; a failed task is refunded in full. quoteId remains accepted, and expires_at only suggests when to refresh. |
| POST | /v1/generations | 201 | generation:create | Create an asynchronous task; submit reference images and referenceMode in the same JSON request. |
| GET | /v1/generations/{id} | 200 | generation:read | Poll the same task using poll_after_ms until it reaches a terminal state. |
| GET | /v1/generations/{id}/content | 200 / 206 | generation:read | Download the output after success; a single byte-range request returns 206. |
Step 1: read model capabilities
Do not hard-code model capabilities. Submit only modes listed in reference_modes, and hide or block incompatible combinations before submission. The minimum example demonstrates a 4-second, 480p, no-audio request with seedance-2.5-oa.
curl --fail-with-body 'https://api.openaiav.com/v1/models' \
--header "Authorization: Bearer $OPENAIAV_API_KEY"Minimum public video example
The sample contains the full flow: create → poll the same task → download. first_frame uses one reference image and adaptive; generateAudio=false avoids requesting audio. Success codes are 201, 200 and 200/206; on failure, inspect HTTP first and then error.code.
# Keep OPENAIAV_API_KEY in an environment variable; do not paste secrets into shell history.
# One Idempotency-Key per create intent: every run of this script makes a new one.
# Reuse it only when retrying this same create. No uuidgen? IDEMPOTENCY_KEY=$(python3 -c 'import uuid;print(uuid.uuid4())')
IDEMPOTENCY_KEY=$(uuidgen | tr 'A-Z' 'a-z')
echo "Idempotency-Key: $IDEMPOTENCY_KEY (reuse this value only when retrying this same create)"
# 1. Create — success: HTTP 201. On failure, the response body contains error.code.
# Lost response (timeout or dropped connection)? Re-run this same command in this shell:
# the unchanged $IDEMPOTENCY_KEY returns the original task instead of creating a second one.
curl --fail-with-body --request POST 'https://api.openaiav.com/v1/generations' \
--header "Authorization: Bearer $OPENAIAV_API_KEY" \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data '{
"model": "seedance-2.5-oa",
"prompt": "A young woman finishes a letter and walks from a stone cottage into the morning sunlight",
"resolution": "480p",
"aspectRatio": "adaptive",
"durationSeconds": 4,
"referenceMode": "first_frame",
"referenceImages": [
"https://developers.openaiav.com/samples/seedance-letter-reference.webp"
],
"generateAudio": false,
"outputFormat": "mp4"
}'
# 2. Save the returned id as TASK_ID. Poll the same id — success: HTTP 200.
curl --fail-with-body 'https://api.openaiav.com/v1/generations/TASK_ID' \
--header "Authorization: Bearer $OPENAIAV_API_KEY"
# Repeat step 2 after poll_after_ms until status is succeeded or failed.
# Never recreate a task whose status is submission_unknown.
# 3. After succeeded, download — success: HTTP 200 (or 206 with Range).
curl --fail-with-body --location 'https://api.openaiav.com/v1/generations/TASK_ID/content' \
--header "Authorization: Bearer $OPENAIAV_API_KEY" \
--output 'result.mp4'Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Required | Use the model value from GET /v1/models; the minimum video sample uses seedance-2.5-oa. |
| prompt | string | Required | 1–4000 characters. Omni reference can address images in array order as @Image1, @Image2, and so on. |
| resolution | string | Optional | Must be present in the selected model's resolutions; the minimum video sample uses 480p. |
| aspectRatio | string | Optional | Must be present in the selected model's aspect_ratios; use adaptive when the model requires it for first-frame requests. For image models that list image_size_options, send a resolution and aspectRatio pair from that list: not every ratio exists at every resolution. |
| durationSeconds | integer | Optional | Specify an integer durationSeconds within the catalog duration_seconds range for video; omission uses the model minimum. The minimum example uses 4 seconds. |
| referenceMode | first_frame | omni_reference | Required with video reference images | Use only with Seedance video models that explicitly list the mode in reference_modes. One request can select only one mode. |
| referenceImages | string[] | Provide with referenceMode | Public HTTPS image URLs. first_frame requires exactly one image; read the omni_reference limit from reference_images_max. |
| inputImages | string[] | Optional | Input images (reference images) for image-to-image, image models only. Each item is a public HTTPS image URL or a data:image/png;base64,…, data:image/jpeg;base64,… or data:image/webp;base64,… URL. Read each model's limits in GET /v1/models: input_images_max, input_image_max_bytes (per image, by URL or data URL) and input_image_mime_types. Reference images can raise the charge. Do not combine them with a video reference mode. |
| generateAudio | boolean | Optional | Defaults to true for video models that support audio. The lowest-cost sample explicitly sends false; the current catalog lists no separate audio surcharge, and GET /v1/models is always authoritative for billing. |
| outputFormat | mp4 | mov | Optional | Choose only from catalog output_formats; the minimum sample uses mp4. |
Reference modes
first_frame
first_frame: the reference image becomes the opening frame and sets the composition. Submit exactly one referenceImages item; aspectRatio comes from the model's aspect_ratios, and locks to adaptive when the catalog offers it (output follows the reference image).
first_last_frame: submit exactly 2 referenceImages. Array order is first frame, then last frame, and the model fills in between. The two images need not share dimensions. Frame modes cannot be combined with omni_reference.
{
"model": "seedance-2.5-oa",
"prompt": "A young woman finishes a letter and walks from a stone cottage into the morning sunlight",
"resolution": "480p",
"aspectRatio": "adaptive",
"durationSeconds": 4,
"referenceMode": "first_frame",
"referenceImages": [
"https://developers.openaiav.com/samples/seedance-letter-reference.webp"
],
"generateAudio": false,
"outputFormat": "mp4"
}omni_reference
omni_reference: reference media locks subject, style and scene consistency without becoming the opening frame. Beyond referenceImages, capable models also accept referenceVideos and referenceAudios; per-type limits are in the table below, and any type the catalog does not declare is a 400 at submit time. Prompts address items by their order within each type: @Image1, @Video1, @Audio1.
{
"model": "seedance-2.5-oa",
"prompt": "Keep the identity and outfit from @Image1; a young woman walks from a stone cottage into the morning sunlight",
"resolution": "480p",
"aspectRatio": "16:9",
"durationSeconds": 4,
"referenceMode": "omni_reference",
"referenceImages": [
"https://developers.openaiav.com/samples/seedance-letter-reference.webp"
],
"generateAudio": false,
"outputFormat": "mp4"
}Live compatibility
One integration for every video model: same endpoint, same fields. Switching models means changing model only. All differences are declared by the catalog, and the table below is that catalog: per model, which reference modes it supports and how much of each media type it accepts. Anything the catalog does not declare is rejected explicitly at submit time, never silently ignored.
First-frame details & last-frame relay
First-frame details: first_frame and omni_reference are mutually exclusive, so one request picks exactly one. Under omni reference you may steer a reference image toward the opening shot via the prompt, but when strict first-frame fidelity matters, submit that image in first_frame mode. On Seedance 2.5, first-frame tasks fix the aspect ratio to adaptive (output follows the first frame). Last-frame relay: pass returnLastFrame: true at creation and a succeeded task also returns last_frame_url (a watermark-free PNG at video size, kept for 24 hours). Feed that URL directly as the next request’s first_frame referenceImages to chain multi-shot sequences, with no pre-registration step.
Reference video pricing
- Reference videos with a measurable duration are billed at a fixed price, which you can preview first. If the duration cannot be measured, the preview is rejected without a charge.
- Call
POST /v1/generation-quotesto preview the current price. URL videos are measured again and repriced at creation; the creation responsereserved_usdgives the actual amount. You may still sendquoteId, but it does not lock the price.expires_atonly suggests when to refresh the preview. - The model catalog sets per-video and total duration, count and minimum billable seconds. Each reference video must be 2–15 seconds for the Seedance 2.0 series and 2–30 seconds for other supported models. Measured input outside the model limits returns 422
REFERENCE_DURATION_OUT_OF_RANGEwithout a charge. - Use a public HTTPS direct link to MP4/MOV. Known files above 200 MiB return
REFERENCE_VIDEO_TOO_LARGE; an unstated size can still be quoted. Private URLs returnREFERENCE_URL_NOT_PUBLIC, and busy probes return 429REFERENCE_PROBE_BUSY. Console uploads are limited to 32 MiB. - Every task is charged its quoted price,
reserved_usd, at creation; on success that is the final charge, and on failure it is refunded in full. A preview'sprice_usdequals what creation would charge at that moment; output duration must still be explicit, not -1. - Public video URLs are probed for the preview and again at creation, then passed directly to the generation service without platform storage or asset registration. Keep the URL content stable. Uploads use a separate registered-asset path. Model restrictions on real human faces still apply.
Image model pricing
- Image models are priced per image by
resolutionandaspectRatio(image_pricing.rates). For thegpt-image-*andnano-banana-*models, these listed prices are for a short prompt with no reference images. - Reference images and longer prompts raise the charge for these models. Before you create a task, call
POST /v1/generation-quoteswith the same body:price_usdis the charge for that request at current prices. A failed task is refunded in full.
Polling and download
Only succeeded and failed are terminal. submission_unknown means acceptance is still being confirmed and result_pending is finalizing: retain the original task ID, keep polling, and never replay creation. After success, use the authenticated /content path rather than relying on any non-Open AIav location. Results are kept for 24 hours (the detail response carries output_expires_at; expired downloads return 410 OUTPUT_EXPIRED, so save promptly).
| Status | Terminal | Action |
|---|---|---|
| processing | No | Accepted; poll again after poll_after_ms. |
| result_pending | No | The result is finalizing. Keep the task ID and poll the same task; do not recreate it. |
| submission_unknown | No | Acceptance is being confirmed; do not replay creation. Keep and poll the original task ID. |
| succeeded | Yes | Succeeded; download the output using /content. |
| failed | Yes | Definitively failed and refunded in full; inspect error_code. |
Retired endpoint migration
/v1/assets, /v1/assets/content, /v1/assets/{id}, /v1/asset-groups and /v1/asset-groups/{id} are retired; a valid key with the corresponding legacy scope receives HTTP 410 / ENDPOINT_RETIRED. New integrations must not call these paths; put referenceMode and referenceImages in the same generation request.Error code reference
Full reference: HTTP status, code, meaning, suggested handling, and the SDK error class. This table shares its data source with the SDK error map; unknown new codes fall back by HTTP status in the SDK.
| HTTP | Code | Meaning | Suggested handling | SDK |
|---|---|---|---|---|
| 401 | UNAUTHENTICATED | Missing or unrecognized API key. | Send Authorization: Bearer oav_…; keep the key server-side. | AuthenticationError |
| 401 | INVALID_API_KEY | The API key is invalid or revoked. | Check the key in the console; rotate it to get a new one if needed. | AuthenticationError |
| 403 | INSUFFICIENT_SCOPE | The key lacks the scope this operation needs. | Creation needs generation:create; polling and download need generation:read. | PermissionError |
| 429 | RATE_LIMITED | Requests exceeded the key rate limit. | Back off per Retry-After / retryAfterSeconds; the SDK handles this automatically. | RateLimitError |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Creation requires an Idempotency-Key header. | Generate a unique key per new request and reuse it on retries. The SDK auto-generates one. | InvalidRequestError |
| 409 | IDEMPOTENCY_KEY_CONFLICT | The same idempotency key was used with a different body. | Use a fresh key for different parameters; keep key and body identical on retries. | InvalidRequestError |
| 400 | MODEL_NOT_FOUND | Unknown model id. | Use ids from GET /v1/models (e.g. seedance-2.5-oa). | InvalidRequestError |
| 503 | MODEL_NOT_AVAILABLE | The model cannot be called right now. | Nothing was charged. Check the model status in GET /v1/models and try again later. | ServerError |
| 400 | INVALID_PROMPT | The prompt is empty or too long. | Prompts must be 1–4000 characters. | InvalidRequestError |
| 400 | INVALID_DURATION | durationSeconds is outside the model range. | Specify the video length as an integer durationSeconds within the catalog range. | InvalidRequestError |
| 400 | INVALID_RESOLUTION | The resolution is not supported by the model. | Pick a value from the catalog resolutions. | InvalidRequestError |
| 400 | INVALID_ASPECT_RATIO | The aspect ratio is not supported by the model. | Pick from catalog aspect_ratios; first_frame often locks adaptive. | InvalidRequestError |
| 400 | INVALID_REFERENCE_IMAGES | referenceImages must be an array of public HTTPS URLs. | Provide https:// image URLs readable for the task lifetime. | InvalidRequestError |
| 400 | INVALID_REFERENCE_IMAGE | A reference image entry is not a valid URL. | The message carries the index (referenceImages[i]); fix that entry. | InvalidRequestError |
| 400 | REFERENCE_IMAGE_URL_NOT_HTTPS | A reference image URL is not public HTTPS. | http, private-network and data URLs are rejected; host the image publicly over HTTPS. | InvalidRequestError |
| 400 | REFERENCE_MODE_REQUIRES_IMAGES | referenceMode was set without reference images. | Provide referenceMode and referenceImages together. | InvalidRequestError |
| 400 | REFERENCE_MODE_NOT_SUPPORTED | The model does not support the requested reference mode. | Choose first_frame / omni_reference per the catalog reference_modes. | InvalidRequestError |
| 400 | FIRST_FRAME_REQUIRES_ONE_IMAGE | first_frame takes exactly one reference image. | Submit exactly one image; use omni_reference for multiple. | InvalidRequestError |
| 400 | TOO_MANY_REFERENCES | Reference image count exceeds the catalog limit. | See reference_images_max in the catalog. | InvalidRequestError |
| 400 | INVALID_INPUT_IMAGES | inputImages must be an array. | Each item is an HTTPS URL or data:image/* base64. | InvalidRequestError |
| 400 | INVALID_INPUT_IMAGE | An input image entry is malformed. | Each entry must be an HTTPS URL or data:image/* base64; the message carries the index. | InvalidRequestError |
| 400 | INPUT_IMAGE_URL_NOT_HTTPS | An input image URL is not HTTPS. | Use an https:// URL or a data URL such as data:image/png;base64,…. | InvalidRequestError |
| 400 | INPUT_IMAGE_TOO_LARGE | An embedded image exceeds 10 MiB. | Compress it below 10 MiB. Models that list input_image_max_bytes apply the same limit to HTTPS URLs: an oversized image fails the task with REFERENCE_INPUT_INVALID, refunded in full. | InvalidRequestError |
| 400 | IMAGE_INPUT_NOT_SUPPORTED | The model does not accept image input. | Only image models with input_images_max>0 accept inputImages. | InvalidRequestError |
| 400 | TOO_MANY_INPUT_IMAGES | Too many input images. | See input_images_max in the catalog. | InvalidRequestError |
| 422 | REFERENCE_DURATION_OUT_OF_RANGE | Measured reference-video duration exceeds the model limits. | Check per-video, total-duration and count limits in the catalog, then preview again; no charge was made. | InvalidRequestError |
| 422 | REFERENCE_DURATION_UNVERIFIED | The reference-video duration could not be measured. | Use a plain MP4/MOV, preferably faststart, from a host that supports Range requests. | InvalidRequestError |
| 422 | REFERENCE_URL_NOT_PUBLIC | The reference-video URL is not public HTTPS. | Use a public HTTPS file URL on port 443 without sign-in. | InvalidRequestError |
| 422 | CONTENT_TYPE_MISMATCH | The reference-video URL does not return the required video format. | Make sure the URL returns MP4/MOV bytes, not a web page or image. | InvalidRequestError |
| 422 | REFERENCE_VIDEO_TOO_LARGE | The reference video exceeds the file-size limit. | Public URLs are limited to 200 MiB; console uploads to 32 MiB. Compress or trim the file and preview again. | InvalidRequestError |
| 429 | REFERENCE_PROBE_BUSY | Too many reference videos are being measured at once. | Retry after retry_after_seconds; no charge was made. | RateLimitError |
| 400 | INSUFFICIENT_BALANCE | Balance is too low for this task. | Top up or redeem a code in Wallet, then retry. | InvalidRequestError |
| 400 | LEGACY_ASSET_REFERENCE_RETIRED | Asset-id references are retired. | Submit referenceMode + referenceImages (public HTTPS) in the same request. | InvalidRequestError |
| 404 | NOT_FOUND | The task does not exist or belongs to another account. | Confirm the task id and the key belong to the same account. | InvalidRequestError |
| 409 | OUTPUT_NOT_READY | The output is not ready yet. | Keep polling the task per poll_after_ms until terminal. | InvalidRequestError |
| 410 | OUTPUT_EXPIRED | The download window (24h after success) has closed. | Save outputs promptly; regenerate after expiry. Use output_expires_at for countdowns. | OutputExpiredError |
Image task failure codes
When an image task ends with status=failed, read error_code and match it below. A failed task is refunded in full. The SDKs raise GenerationFailedError with the same code.
| Code | Meaning |
|---|---|
| CONTENT_POLICY_REJECTED | The content did not pass review. Revise it before submitting again. |
| REFERENCE_INPUT_INVALID | Check the reference format, size and model requirements. |
| REFERENCE_SOURCE_UNREACHABLE | The reference URL cannot be read. Check its expiry or upload again. |
| REFERENCE_SERVICE_UNAVAILABLE | Reference processing is unavailable. Contact support with the task ID; replacing the image is not required. |
| GENERATION_INPUT_INVALID | The generation parameters do not meet this model’s requirements. |
| GENERATION_RATE_LIMITED | The generation service is temporarily rate limited. Try again later. |
| GENERATION_REJECTED | The generation request was rejected. Contact support with the task ID to verify why. |
| GENERATION_SERVICE_UNAVAILABLE | The generation service is temporarily unavailable. |
| GENERATION_FAILED | The task could not complete. Contact support with its ID. |
Codes returned only in the console
| HTTP | Code | Meaning | Suggested handling |
|---|---|---|---|
| 400 | REFERENCE_UPLOAD_NOT_FOUND | The upload reference is missing, expired, or owned by another account. | Re-upload in the Playground and run again (uploads are short-lived). |
| 429 | UPLOAD_QUOTA_EXCEEDED | Too many unused uploads. | Run a task that uses your uploads, or wait for them to expire. |
| 429 | WEBHOOK_ENDPOINT_LIMIT | Webhook endpoint limit reached. | Delete or disable an unused endpoint. |
| 400 | INVALID_WEBHOOK_URL | The callback URL is not publicly reachable HTTPS. | Use a public HTTPS address; private/loopback hosts are rejected. |