Skip to main content
The Aurous Labs API uses date-pinned versioning. Send Aurous-Version: 2026-05-15 (or any prior date) on every request to opt into a specific contract; we echo the applied version on every response. Missing the header falls back to your team’s default_api_version (current at signup). Breaking changes ship under a new date; non-breaking additions are listed under the current date.

2026-07-31 — Machine-readable error_code, honest interruption messaging, retryable 503s on account/billing checks

A non-breaking batch that makes generation failures easier to handle programmatically: a machine-readable error_code on every generation, honest interruption messaging, and 503 responses with a Retry-After header when the platform can’t momentarily verify your account or billing state.
  • New error_code field on every generation. POST/GET /v1/images (create, detail, list — video rows included) and POST /v1/videos (create), plus webhook data payloads, now carry error_code — a machine-readable, closed-enum reason for status: failed: generation_interrupted, reference_preparation_failed, content_filtered, or generation_failed. Switch on error_code, not on error_message (human-readable prose that can change without notice). null on non-failed generations and on failures recorded before this field existed — always keep a default branch in your switch statement. Purely additive. Related transient failures that don’t reach a terminal generation row instead surface as 503 provider_unavailable — see that page for retry guidance.
  • Interrupted generations say so plainly. A generation interrupted before completion that couldn’t be recovered now reports error_code: "generation_interrupted" with error_message: "The generation was interrupted before it could complete and could not be recovered. Credits have been refunded." — and the refund is guaranteed on this path.
  • Account/billing checks now answer 503 with a Retry-After header when the platform can’t momentarily verify your account or billing state. Any V1 endpoint that checks that state — including POST /v1/images, POST /v1/videos, their /estimate counterparts, and GET /v1/team / /v1/usage / /v1/balance / /v1/loras — returns 503 provider_unavailable with a Retry-After header (typically 5). See provider_unavailable.
  • 503 responses are retry-safe under Idempotency-Key. A 503 is never cached against your key — retry the exact same Idempotency-Key and it re-executes normally; you will not see 409 idempotency_key_in_use as a result of a 503. See Idempotency.
  • No action required beyond honoring Retry-After on 503 and preferring error_code over error_message going forward.

2026-07-24 — Styles are back: prompt styles, auto-matching with opt-out, compose with acts

Styles return to POST /v1/images as prompt styles — richer look control on the current image models — with automatic matching, full composability with composition acts and subjects, and honest signaling (style echo + warnings[]) so the applied outcome is always observable. Batch count now works on every request shape. Existing integrations keep working; the behavior notes below are worth reading if you pin lora_id or key off estimate quotes today.
  • lora_id is now tri-state. Omit it and style matching runs automatically when your prompt clearly names a look (conservative — most prompts resolve to no style); send null to disable matching for a request; send an id (or slug) from GET /v1/loras to pin a style. See Create an image → Using a style.
  • Every generation echoes style: { id, name } (or null) — on creates, GETs, list rows, and webhook data payloads. style.id is the same lora_* identifier the catalog serves and round-trips into lora_id. A new, always-present, nullable key: integrations that don’t read it are unaffected.
  • Styles compose with acts and subjects. lora_id + action_id and lora_id + subjects[] are now valid combinations (previously 400 parameter_invalid_combination). One rule to know: a composition-act id sent as lora_id acts as the pin itself, so it can’t be combined with a different action_id. lora_id + context_images remains invalid.
  • Retired style ids keep working. An id with a designated successor applies the successor style (echoed in style); other retired ids generate without a style and add a warnings[] entry (style_retired_plain). A small set of discontinued styles return the new 400 style_retired — on create and estimate.
  • New warnings[] on create + estimate responses. Non-fatal request adjustments — a retired style pin, or a parameter with no effect on the selected generation path (parameter_ignored). Omitted when empty; never on GETs, lists, or webhooks; the code set is open, so ignore unknown codes. Idempotent replays return the original warnings verbatim.
  • count works everywhere, in parallel. Every request shape — plain, subjects[], context_images — now delivers up to 4 images per request, billed per image; if part of a batch fails you receive the successful images and the difference is refunded automatically (image_count reflects delivered). Estimates quote count × accordingly: subjects/context_images estimates previously quoted a single image and now quote the full batch.
  • count must be a whole number. count: 3.5 now returns a validation 400 (it was previously accepted and rounded down).
  • Behavior change: with lora_id omitted, a prompt that clearly names a look may now generate with a matched style where it previously always generated plain. The style echo makes this visible; send lora_id: null if you need guaranteed-unstyled output. Applies across all version pins.
  • Behavior change: styled generations run on the current image models, which serve prompt + size and ignore the legacy sampler fields; a sampler field sent on such a request is reported via warnings[] rather than silently dropped.

2026-07-22 — Multi-person video casts, reference-media uploads, and a contract-closure batch

One breaking removal (a webhook field with zero recorded uses) plus a set of additive capabilities and precision fixes that close the drift between the documented contract and live behavior.
  • Breaking: the per-request webhook_url field is removed from POST /v1/images and POST /v1/videos. It never delivered what the docs promised (wrong event names, an internal id, an unsanitized field subset) and has zero recorded uses across all teams, all-time — which is why it is removed outright rather than gated behind a new Aurous-Version date. Requests that still send it receive 400 parameter_unknown under every version pin. Use registered webhook endpoints instead (POST /v1/webhook_endpoints): the full event catalog (image.* and video.* including .cancelled, character.*, balance events), signed payloads, delivery logs, and retries.
  • Cast up to 2 people in a video via subjects[] on POST /v1/videos — each entry a saved character (char_<ulid>) or its own group of 1–4 reference images (file_<ulid> or HTTPS URLs), freely mixed; the same wire grammar the images surface uses. A cast may ride a reference_video_url — the clip supplies the motion while the cast anchors identity. Casts don’t change the price. The legacy single-subject fields (character_id, reference_image_urls) remain fully supported and now carry the advisory Deprecation: true header on the video routes too.
  • Upload reference media. POST /v1/files accepts purpose: reference_video (MP4/MOV, ≤50 MB) and purpose: reference_audio (WAV/MP3, ≤15 MB); reference_video_url / reference_audio_url accept the returned file_<ulid> anywhere an HTTPS URL was accepted. Format, duration, and resolution validate at upload time — an accepted upload can never fail those checks at create. FileResponse gains duration_s (parsed clip duration; null for images).
  • Audio references pair with more companions. reference_audio_url now anchors to a reference video, a subject, or a pinned model — previously it required a reference video.
  • Video generations echo their cast. subjects[] appears on video generation responses with the same ordered, opaque projection images use: [{type, character_id, image_count}] — never URLs, never internal ids. null on generations without a cast.
  • enhancer_outcome joins the documented contract — the informational field behind “Enhancer declined this prompt — your raw text was used.” Always present, nullable. Treat it as an open set: new values may be added without a version bump, so don’t branch control flow on it.
  • GET /v1/usage speaks succeeded. Status facet values now use the same public vocabulary as the generations surface (previously the internal completed spelling leaked through). The status filter accepts both spellings.
  • List / webhook / detail parity fixes. GET /v1/images list rows and webhook data payloads now carry the full documented GenerationResponse shape (negative_prompt, cfg_rescale, denoise_strength, seed, aurous_version, creation_request_id had drifted off some surfaces), and the generation status enum is defensively frozen to its 5 documented values.

2026-07-18 — Raw Seedance video API + per-model, token-based video pricing

Two video additions ship together: a provider-native “raw” Seedance surface for teams that already build against the Seedance SDK, and per-model, token-based video pricing so the rate you read is the rate you pay. Both are additive — existing POST /v1/videos integrations are unaffected.
  • Raw Seedance task API. New POST /v1/contents/generations/tasks, GET /v1/contents/generations/tasks/{id}, GET /v1/contents/generations/tasks, and DELETE /v1/contents/generations/tasks/{id}. The request and task bodies are byte-exact to the native Seedance video API — Aurous forwards your Ark-SDK payload verbatim, including fields not yet documented here, wrapped in the same credit holds, Idempotency-Key support, rate limits, and typed error envelope as the rest of the V1 API. Point your existing Seedance SDK client at Aurous and go — see raw Seedance access.
  • Video generation is priced per model on your actual token usage. Each video model carries its own token rate, and you are billed for the tokens a generation actually consumes. Published rates exactly match what you are charged: a ceiling is held while the task runs, and the final charge settles on the provider’s reported tokens at success — a failed, expired, or queued-cancelled task charges nothing and releases the hold in full. Read the live rate for any model from GET /v1/models; the day-one rate card and the formula behind it are on Seedance models & pricing.
  • Two new lower-cost tiers. seedance-2.0-fast and seedance-2.0-mini join the full-quality seedance-2.0, giving you cheaper options when you don’t need the flagship model. Address any model by id or alias.
  • Video-input tasks meter reference-video seconds. A task whose content[] includes a reference video is billed for the reference footage it ingests in addition to the video it generates, so the receipt reflects the full cost of the work.
  • No version bump. Video rates are per-model and DB-driven — they can change without a new Aurous-Version, the same way chat and embedding rates do. Always read the current rate from GET /v1/models rather than hard-coding it; see the rate-mutability asymmetry. Each charged task snapshots the rate in force when it was created, so the amount you were quoted is the amount you pay.

2026-07-16 — Reference video/audio on POST /v1/videos

POST /v1/videos accepts a third input mode alongside text and frames: reference_video_url, an HTTPS URL to your own clip. The generation either borrows the clip’s motion for a new scene (video_task: "reference", the default) or continues the clip itself (video_task: "extend"). Optionally pair it with reference_audio_url to guide the generated soundtrack. This is additive — existing text-to-video and image-to-video requests are unaffected.
  • New request fields on POST /v1/videos and POST /v1/videos/estimate. reference_video_url, reference_audio_url, video_task (reference | extend), extend_direction (forward | backward). See the endpoint reference for size/duration/format limits and the full parameter-combination rules.
  • New response fields. video_task, extend_direction, reference_video_url, reference_audio_urlnull on every generation that did not use this mode, including all pre-existing rows.
  • inference_type gains r2v (reference-to-video). A generation that used your own reference_video_url reports inference_type: "r2v"; frame-driven, subject-driven, and pinned Action/template videos continue to report i2v. Treat unrecognized inference_type values as opaque.
  • New error codes. reference_fetch_failed (couldn’t fetch your reference URL) and reference_media_invalid (fetched, but failed format/duration/resolution validation) — see Errors.
  • Timing. The reference is prepared inside processing — typically well under a minute, with a 10-minute budget before the generation fails with a full refund. A reused reference (identical bytes) skips preparation. If preparation fails, retry with a new Idempotency-Key.

2026-07-16 — Subject-driven video, a video model catalog, and adaptive duration by default

POST /v1/videos now has the same generation surface as POST /v1/images: pin or auto-match a video model, drive a video from a saved character or reference images, and price adaptive-length videos honestly. All changes are additive — existing integrations keep working — with two behavior notes below worth reading if you key off video_lora_id or inference_type today.
  • POST /v1/videos. Accepts character_id and reference_image_urls (up to 6), mirroring the same fields on POST /v1/images — the subject is anchored across the generated video. Mutually exclusive with each other (400 mutually_exclusive_input) and with first_frame_url / last_frame_url (400 parameter_invalid_combination). See Create a video.
  • GET /v1/video_loras now lists real, pinnable video models (previously effectively empty). Pin one with video_lora_id, or omit it and the platform matches your prompt to a suitable model automatically. Whichever model ends up behind a generation — pinned or matched — is echoed back on the response as video_lora_id and video_lora_name.
  • Adaptive duration is the default. Omit duration (or pass -1) and the model picks the natural length for your prompt, 4–15 seconds, and you’re billed for the length actually delivered — not a flat ceiling. POST /v1/videos/estimate prices this as a range (amount_minamount_max); the ceiling is reserved up front and the difference is released once the real length is known. Pin a whole-number duration (4–15) for a fixed length at a fixed price.
  • cost.refunded is a new field. A failed generation now reports cost: {"amount": 0, "refunded": true} instead of a bare zero, so you can tell “never charged” apart from “charged nothing this time.” Present on both image and video generations.
  • Behavior change: a prompt-only video request that previously always rendered as plain text-to-video may now be auto-matched to a video model, producing a different look than before. Pin video_lora_id explicitly if you need the exact prior output character.
  • Behavior change: a subject-driven video (character_id or reference_image_urls) reports inference_type: "i2v" on the generation response — the same value used for first/last-frame videos. There is no separate subject-specific value.

2026-06-21 — Transient storage failures now return 503 + Retry-After

Upload and character-create routes that touch object storage now surface a transient storage failure as a retryable 503 provider_unavailable carrying a Retry-After header, instead of a generic 500. This is a non-breaking improvement — no action required beyond honoring the new retry signal.
  • POST /v1/characters/uploads/init, POST /v1/characters/uploads/classify, and POST /v1/characters. A transient storage error now returns 503 provider_unavailable with a Retry-After header instead of a generic 500. Retry on 503 after the number of seconds in Retry-After. Applies across all version pins.

2026-06-21 — Character synthesis rate limit raised to 15/min

The per-team rate limit on character synthesis increased from 6 requests/minute (burst 12) to 15 requests/minute (burst 30). This is a loosening — no action required.
  • POST /v1/characters, POST /v1/characters/:id/refs/regenerate, POST /v1/characters/:id/resynthesize. These three routes share one per-team bucket, now 15/minute sustained with a burst of 30. Each create or resynthesize fans out to four image generations internally, so the bucket is sized to match the POST /v1/images budget — 15 × 4 = 60 image generations/minute. A burst of regenerations or resynthesizes draws down the same budget as new creates.
  • Rate limits are not version-pinned. The live X-RateLimit-Limit response header is always the authoritative current value; we raise limits without notice and announce any reduction in advance here. Applies across all version pins.

2026-06-19 — Character synthesis is now asynchronous + character webhook events

POST /v1/characters with generate: true no longer blocks until the character is rendered. It returns immediately with status: "synthesizing"; the synthesis runs in the background and the character transitions to ready (or failed). Poll GET /v1/characters/:id, or subscribe to the new character webhook events.
  • POST /v1/characters. Now responds 201 with status: "synthesizing" instead of returning the finished character. An optional client_reference_id (≤ 256 chars) is echoed on the response and on every character webhook. Behavior change: callers that relied on the synchronous response must switch to polling GET /v1/characters/:id until status is ready or failed, or subscribe to webhooks. Applies across all version pins.
  • New webhook events. character.completed (reached ready; payload carries the reference renders), character.failed (provider error, moderation, or timeout — error_message carries a customer-safe code), and character.cancelled (DELETE /v1/characters/:id on an in-flight synthesis). Subscribe via the events array on a webhook endpoint, or in the dashboard webhook settings.

2026-06-03 — Documented platform defaults now applied to omitted generation params

POST /v1/images now applies the platform default for steps, guidance_scale, cfg_rescale, and denoise_strength when you omit the parameter and the selected style does not define its own default. Previously these defaults were documented on the request fields but only enforced by the dashboard, so an API caller who omitted them fell through to the provider’s raw default.
  • POST /v1/images. Omitting cfg_rescale resolves to 0.7, steps to 11, and guidance_scale to 4.3. Omitting denoise_strength on a request that has references resolves to 0.6 (it remains ignored on bare text-to-image requests). Precedence is unchanged: an explicit request value wins, then the selected style’s default, then the platform default.
  • No request or response shape change. No fields added or removed; the response continues to echo only the values you supplied — omitted params are resolved server-side and not reflected back.
This aligns behavior with the already-published default values on the request fields, so it applies across all version pins rather than shipping under a new date. The only observable change is for a caller who omitted cfg_rescale and relied on the provider’s raw default. Zero customers were affected at release time.

2026-05-24 — Embedding video rate removed; video_url input rejected

The embedding_pricing.video field on GET /v1/models has been removed. Embedding billing has always folded video_url input into the visual bucket — the provider extracts frames from videos and bills them as visual tokens. The previously published embedding_pricing.video rate never actually fired. To prevent the contract from misleading integrators, the field is now gone.
  • GET /v1/models. The embedding_pricing object on embedding-kind rows no longer contains a video key. text and visual are unchanged. The visual field description now notes explicitly that image_url parts bill at this rate; video_url input is no longer accepted (see below).
  • POST /v1/embeddings + POST /v1/embeddings/estimate. Any video_url content part returns 400 embeddings_video_unsupported (new error code). To embed visual content from a video, extract a representative frame in your pipeline and submit it as image_url; it bills at the visual rate.
  • Renamed error code. embeddings_video_too_many_parts (which previously fired for ≥ 2 video parts) was renamed to embeddings_video_unsupported and now fires for ≥ 1 video part. Update any integration that caught the old code on a single-video payload.
  • Response shape compatibility. POST /v1/embeddings/estimate retains tokens.video and breakdown.input.video on the response shape, both always 0, for one release cycle so existing SDKs that read them don’t break. Both fields will be dropped in a follow-up release.
This is a breaking change for any caller that pinned the embedding_pricing.video field or sent video_url parts. Embedding rates are explicitly mutable per the rate-card mutability note on /v1/models and /v1/chat/completions, so the change applies across all version pins. Zero customers were affected at release time.

2026-05-11

  • Image size presets reshaped. size now follows <tier>_<ratio> form: 2k_1_1, 2k_3_2, 2k_2_3, 2k_4_3, 2k_3_4, 2k_16_9, 2k_9_16, 2k_21_9, plus the same eight ratios at 4k_*. Sixteen presets total. The previous six names (square, landscape, portrait, hd_square, hd_landscape, hd_portrait) return 400 invalid_format.
  • Custom dimensions. width and height now require both, in [1024, 4096] per side, and are snapped server-side to multiples of 32. The response width/height reflect the post-snap value (may differ from the request by up to 31 px per side).
  • Mutual exclusion. Sending both size and width/height returns 400 parameter_invalid_combination. Sending only one of width/height returns 400 missing_field.

2026-05-15 (v1.0)

The first stable date-pin. Everything documented elsewhere on this site is what 2026-05-15 means.

Resource shape

  • /v1/images and /v1/videos are the create + read paths for image and video generations.
  • /v1/files is the upload path for reference images you reuse across generations.
  • /v1/webhook_endpoints registers + manages signed webhook deliveries.
  • /v1/loras and /v1/video_loras are the public catalog of styles.
  • /v1/team, /v1/balance, /v1/usage, /v1/usage/events cover account and metering. Per-model chat_pricing / embedding_pricing ships on GET /v1/models and reflects the caller’s effective rate including any per-team overrides.

Pricing

  • Image cost = image.base × count plus an optional enhance line when enhance_prompt: true. Refs and character_id are free — neither shifts the price.
  • Video cost = base_per_second × resolution_factor × duration_s (multiplicative). The breakdown carries the inputs; cost.amount is the product.
  • Team-level discounts surface as a discount_factor line in the breakdown when the team has a negotiated rate.

Webhook event types — v1.0 lineup

v1.0 ships eight event types. Four additional event types are reserved for a later date-pin — the underlying status transitions aren’t yet wired into the production code path, so they’re documented but never fire today. Currently fired:
  • image.completed / image.failed / image.cancelled
  • video.completed / video.failed / video.cancelled
  • usage.balance_low (per-team threshold; 1-hour debounce)
  • webhook.endpoint_disabled (auto-disable notification)
Reserved for a later date-pin (NOT fired today):
  • image.expired / video.expired — fires when a generation exceeds the 5-minute polling window without a terminal status. Requires a polling-timeout sweeper that flips status from processing to expired. Until then, stuck generations remain processing; the worker eventually fails them or the cancel endpoint resolves the hold.
  • image.moderation_rejected / video.moderation_rejected — moderation rejection currently throws 400 invalid_request from the create endpoint without inserting an inferences row, so there is nothing to fire a webhook against. Future change: insert the row in moderation_rejected state and fire the event.
If you subscribe with events: ["*"], the snapshot taken at registration time includes only the events that exist when you register. A future date-pin that adds *.expired or *.moderation_rejected will require you to update your registration explicitly — ["*"] does NOT auto-subscribe across date-pins.

Status enum

Generation status is one of: pending | processing | succeeded | failed | cancelled. expired and moderation_rejected are reserved enum values per the spec but never fire as wire values in 2026-05-15 (see “Webhook event types” above for the same reason).

Usage events — fields reserved for a later date-pin

GET /v1/usage/events returns line items with id, type, amount, currency, generation_id, hold_id, description, and created_at. Two fields commonly seen in similar ledger APIs (Stripe events, etc.) are intentionally NOT in 2026-05-15:
  • balance_before / balance_after — the team’s credit balance immediately before and after the event. Computing these accurately requires an authoritative balance-tracking column on usage_events (or a transactional bookkeeping query at write time). Reserved for a follow-up date-pin once the bookkeeping column is added.
If you need a per-event running balance today, sum amount across rows in chronological order with the type-derived sign (hold / charge / adjustment with negative description debit; release / refund / topup / positive adjustment credit).

2026-05-22

LLM pricing transparency redesign. Three breaking changes, one removal, one units change.

Model rename (breaking)

Four customer-facing slugs were renamed. Old slugs return 404 model_not_found. Three Grow 2.0 chat models (aurous-grow-2.0-mini, aurous-grow-2.0-lite, aurous-grow-2.0-code) are unchanged but now share the family-wide 50% markup (previously 15%). No call-site change required.

/v1/pricing removed (breaking)

GET /v1/pricing now returns 404. Per-model rates are embedded directly in GET /v1/models under chat_pricing (chat models) and embedding_pricing (embedding models). One less round-trip; the data is identical and now lives alongside model metadata.

/v1/models response shape (breaking)

The model response gained two nullable siblings — chat_pricing and embedding_pricing — replacing the previous aurous_metadata.credits_per_1k_* fields. Per founder direction the unit is now per million tokens (credits_per_M), not per 1K. Important — 1000× scale change. Customers who hardcoded the previous credits_per_1k_* values into FinOps spreadsheets, monitoring dashboards, or rate-card exports must either (a) divide credits_per_M by 1000 to recover the per-1K number, or (b) update math to use per-million directly. New response shape:
For embedding models, chat_pricing is null and embedding_pricing carries text / visual / video per-modality rates.

60-second jq recipe — migrate a FinOps script

If you were pulling aurous_metadata.credits_per_1k_input_tokens from /v1/models, the equivalent value lives at chat_pricing.input.credits_per_M / 1000:
For embedding models, swap chat_pricing.input/output for embedding_pricing.text/visual/video.

What didn’t change

  • Receipt shape on /v1/chat/completions and /v1/embeddings responses (usage, credits_charged, breakdown) is unchanged.
  • pricing_version in the receipt continues to snapshot the effective rate at hold time, so replays remain deterministic.
  • Image and video pricing on /v1/images, /v1/videos, /v1/loras, /v1/video_loras is unaffected by this release.
  • Aurous-Version pinning continues to work; chat and embedding rates intentionally track the most-recently published rate version (per spec — LLM provider economics shift on weekly cadence).