Skip to main content
deAPI exposes an OpenAI-compatible /v1/* API. Point the official OpenAI SDK (Python, Node, or plain cURL) at our base URL, use your deAPI token as the API key, and call our models with the request and response shapes you already know. This is a translation layer over the deAPI inference network — not a rebrand of OpenAI’s models. Everything below documents exactly where the two contracts match and where they deliberately differ. It is the same approach used by Groq, Together AI, and Fireworks AI. Base URL: https://oai.deapi.ai/v1

Quick start

Get your API key at app.deapi.ai/dashboard. New accounts receive a $5 bonus — no credit card required.

Authentication

Generate an API token in the deAPI panel. It looks like dpn-sk-<id>|<token> and goes into the standard OpenAI Authorization header:
The dpn-sk- prefix is required by this gateway and applies only here — the native API at https://api.deapi.ai takes the raw key without it. Note the literal | in the value: in a shell it must be quoted. Every request validates the token against your deAPI account, so a revoked token stops working immediately.
Keys with an IP allowlist do not work through this endpoint. Use a key without an IP allowlist here, or call the native API at https://api.deapi.ai instead.

Calling from a browser

/v1/* sends CORS headers, so openai-node works in the browser with dangerouslyAllowBrowser: true, as does a plain fetch from any origin. Preflight is answered without authentication (204, Access-Control-Max-Age: 86400), every header the SDK sends is accepted, and these are exposed to scripts:
So a failure in the browser still gives you the id to quote in a support ticket.
Read the flag name before you use it. Anything you put in browser JavaScript is readable by everyone who loads the page, and that includes your deAPI token — which is a billing credential, not a session cookie. This is a viable path for a demo, an internal tool behind your own auth, or a prototype. It is not one for a public site. For anything user-facing, keep the token on your server and proxy the calls.

Timeouts

Image, audio and edit jobs run on a distributed GPU network and the gateway holds the HTTP connection until the job finishes. A request may legitimately take up to 10 minutes, which is exactly where the OpenAI SDK’s own 600 s default sits. For heavy work (image edits, long transcriptions, n > 1) raise it:
Video generation is the exception — it is asynchronous by design and returns immediately. See Video.

Supported endpoints

Paths that are recognised but not implemented. POST /v1/videos/edits, POST /v1/videos/extensions, POST /v1/videos/characters, GET /v1/videos/characters/{id}, GET /v1/videos (list), DELETE /v1/videos/{id}, POST /v1/videos/{id}/remix.These exist in the OpenAI spec but have no counterpart in our backend. They answer 404 with code: "endpoint_not_implemented" — the code, not the status, is what tells you the path was understood and there is no typo on your side. Branch on code; a plain 404 from a real endpoint carries a different one (model_not_found, video_not_found).They used to answer 501, and the reason we changed it is worth knowing. Both official SDKs retry every response with status >= 500. A mistyped URL therefore cost you four requests with backoff and surfaced as InternalServerError — your monitoring reported an outage that was not happening. 404 maps to NotFoundError and is not retried, so a typo now fails once, immediately, with a message that names the path.Two codes share the 404, and the difference is only diagnostic: an unrecognised path answers endpoint_not_implemented, while extra segments below a route we do own (/v1/videos/foo/bar) answer endpoint_not_found. Treat them the same way — you have the wrong URL.Calling a real endpoint with the wrong verb is different again: 405 method_not_allowed, and the message names the verb to use.

Models

GET /v1/models returns the models available to your account. The catalog is plan-dependent, so two accounts can legitimately see different lists.
GET /v1/models/{model} (client.models.retrieve(...)) returns a single entry in the same shape. Unknown, hidden, or out-of-plan models answer 404 model_not_found. The response shape is standard: {id, object, created, owned_by}, where owned_by is always depin. created is the date the model first became available on this gateway, not the date the model itself was built — the inference backend publishes no such date, and this is the only one we can state truthfully. Cohorts that arrived together share a timestamp, so it does not order models within a release. Entries used to report 0 unless the model was also listed on OpenRouter. Every model now carries a real timestamp. Catalog reads are cached for 5 minutes, so a model added or removed on the deAPI side appears (or disappears) within that window.
For the full native catalog with capabilities, limits, defaults and LoRAs, use the Model Selection endpoint or the Models guide.
Partner models are native-only. deAPI’s catalog includes hosted partner models whose IDs look like the ones you already type — gpt-image-1.5, gpt-image-2, nano-banana-2. None of them is routable through this gateway. They are absent from GET /v1/models and every call naming one answers 404 model_not_found, on /v1/models/{model} and on POST alike.This trips people up in exactly one way: gpt-image-2 is a real deAPI model and the natural thing to send to an OpenAI-shaped endpoint, so the 404 reads like an outage rather than a routing rule. Reach partner models through the native API; they are billed differently from the network models, which is the reason they are not folded in here.

When a model name is refused

A name your account cannot use fails in one of three ways, and the code says which: Build your integration against GET /v1/models. It is already filtered to your account, so every name in it resolves on the endpoint it belongs to — which makes it the list to check first when a model name is refused.

Why there are no OpenAI model aliases

We publish native model IDs only. Aliasing dall-e-3 onto Flux1schnell would promise an OpenAI parameter contract — its sizes, qualities and defaults — that our models do not implement. A wrong picture is worse than an explicit model name, so model="dall-e-3" returns 404 model_not_found — on POST endpoints as well as on GET /v1/models/{model}.

Migrating from OpenAI

Three parameters change. Everything else — request format, response schema, error envelope — follows the OpenAI specification.

Embeddings

dimensions does not resize the vector. Bge_M3_FP16 always returns 1024 floats, whatever you pass. Size your index from the response, not from the request.
Array inputs are capped at 100 items per request; above that you get 400 input_too_large. The batch is all-or-nothing: the first failure cancels the rest, so you receive either every vector or a single error — never a partially filled data[].
The cap is not your rate limit, and batching does not shelter you from it. The backend embeds one string per call, so an array of N strings is N jobs and counts as N requests against your account’s allowance. On a plan with a small per-minute bucket a large array can exhaust it, return 429 for the whole request, and still have spent the allowance on the items already dispatched. Size your arrays against your plan’s rate limit, not against the 100-item cap.
usage.prompt_tokens is always 0. The backend does not report token counts and inventing one would be worse than reporting zero. total_tokens mirrors it.

Image generation

revised_prompt is part of the OpenAI shape but is never populated here, so the key is absent — none of our models rewrites your prompt. quality="auto" is the SDK default and means “pick the best setting for this model” — identical to omitting the field. Quality presets are defined per model because distilled models (Flux Schnell: 2–8 steps) and full models operate in disjoint ranges. A generic “high = 50 steps” rule would silently produce garbage on distilled models.

Rejected fields

Accepted with no change in behaviour: stream=false, partial_images=0, background="opaque" | "auto", output_format="png" — these describe what we already do. moderation and style are accepted and explicitly declared as not affecting the image.
Output is PNG, RGB, no alpha — measured on the bytes, not assumed. With response_format="url" you get a pre-signed results.deapi.ai link valid for 5 hours (X-Amz-Expires ≈ 18000, counted from the X-Amz-Date in the same query string). There is no expiry field in the JSON body, and unlike a video there is no route that re-signs the link — once it lapses the image is gone from your side. Download promptly, or request b64_json and skip the question.

Image editing

Both content types from the spec are supported. Both are instruction-following editors, not inpainting models. The gateway itself accepts a multipart body up to 50 MiB, but the platform caps a single image at 10 MB — see Limits & Quotas → File Uploads.

multipart/form-data

Fields: image (one or more files), prompt, model, n, size, quality, response_format, user. File type is detected from the bytes, not from the declared part header.

application/json

  • data: URLs are decoded locally, with no outbound traffic.
  • Plain URLs are fetched by the gateway: 10 MB limit, publicly reachable addresses only.
  • file_id → 400 file_id_not_supported (there is no Files API here).
  • A bare string instead of an object → 400. We do not reinterpret it.
Ordering, if you care what the gateway touches before it gives up: the model, the required fields and every unsupported-parameter check (stream, output_format, mask, input_fidelity, …) are settled before any address is fetched. The two per-model limits — size and n — are checked after, so an oversized n on an otherwise valid request will still have cost you one outbound fetch of the URL you supplied. A data: URL is decoded locally and never leaves the gateway either way.

Response

Identical envelope to /v1/images/generations — the two endpoints share a shape on the wire.
With response_format="b64_json" each element carries b64_json instead of url. With n=2 you get two elements, one per job.

Rejected fields on edits

Everything from image generation, plus: One deviation in your favour: we accept quality="hd" here, even though the spec restricts hd to /images/generations. The alias is defined per model and produces a sensible preset, so rejecting it would break working integrations.

Text-to-speech

Returns raw audio bytes, not JSON.
The body is the audio file itself, not JSON:
Errors on this endpoint still come back as the usual JSON envelope with Content-Type: application/json, so branch on the status code before reading the body as audio.
Speed is pinned at 1.0 on Chatterbox and the whole Qwen3 family — any other value returns 400 speed_out_of_range. Those models have no tempo control, and pretending otherwise would bill you for a request executed against your instructions. OpenAI voice names work on Kokoro through aliases: alloy → af_alloy, echo → am_echo, fable → bm_fable, onyx → am_onyx, nova → af_nova, shimmer → af_sky. On Chatterbox and Qwen3 they map to that model’s own voices.

Describing a voice instead of picking one

Qwen3_TTS_12Hz_1_7B_VoiceDesign takes a natural-language voice description:
  • instructions on a model with a voice list → 400 instructions_not_supported, and the message names the model that does support it. We refuse instead of ignoring, because a silently dropped field means paying for half of what you asked for.
  • The voice-design model without instructions → 400 instructions_required.
  • voice is accepted and ignored on that model — OpenAI types the field as required, but this model has no voice list.

How the language is chosen

The OpenAI spec has no language field for speech, and our backend needs one. The first rule that yields a value wins:
  1. An explicit language in the request (our extension).
  2. The voice slug prefix, where the model publishes a mapping. Kokoro:
  3. The model’s default language.
  4. en-us.
No language auto-detection. Kokoro voices are hard-wired to a language and the model does not detect language from the text. All six OpenAI voice aliases map to English voices, so non-English text needs an explicit voice from the right family — or an explicit language. Drop-in compatibility is incomplete here.

Rejected fields on speech

stream_format="audio" is accepted — it describes the current behaviour.

Transcription

The gateway accepts a multipart body up to 50 MiB, but the platform caps a single audio file at 20 MB and a single video file at 50 MB, within a global 75 MB request body cap. See Limits & Quotas → File Uploads.

Response formats

srt and vtt return 400 format_not_supported — the backend returns text and we do not synthesise subtitle files. Speakers in diarized_json are numbered by first appearance (A, B, …) and are stable within one response, not across calls — the same person may be A in one recording and B in the next. This matches OpenAI’s behaviour without speaker references. Diarization is available on exactly one model (WhisperLargeV3Ct2). Requesting it elsewhere returns 400 before a billable job is created, and the message names the model that can do it.

Word timings come from the model, never from us

timestamp_granularities=["word"] is served only by models that publish word-level support — today WhisperLargeV3Ct2. On WhisperLargeV3 it is a 400:
We used to answer 200 here and build words[] by splitting each segment’s duration evenly across its words. In a 3-second, 9-word clip every single word came back as exactly 0.333 s — numbers you had no way of telling apart from a measurement. Since a model that does the job properly sits in the same catalog, refusing and naming it is the honest answer.

Fields in verbose_json that carry no real measurement

The shape is always complete so drop-in clients see a stable set of keys, but some values are placeholders rather than measurements: The language field in the response is a real detection on WhisperLargeV3Ct2 (the model’s own detection wins over your hint, returned as a Whisper-style name such as english). On WhisperLargeV3 it echoes your request instead.

Rejected fields on transcription

Accepted, but with no effect on the transcript: prompt, temperature, keywords[]. These are best-effort hints at OpenAI too, and we accept them so that existing code keeps working — but they do not change the transcript. If you are counting on prompt to force terminology or temperature=0 to give determinism, it will not happen here.
stream=false is accepted; it describes what we already do.

Video

The only family that is asynchronous for the client as well: POST returns immediately and you poll for the result. Each model validates seconds and size against its own list — the table is a union, not a menu that applies everywhere.
A failed job carries an error object inside the video object and the GET itself still returns 200 — the request succeeded, the job did not. Branch on status, not on the status code.
GET /v1/videos/{id}/content returns the file:
Before the job completes the same route answers 409 video_not_ready, and 502 video_failed if it failed:
?variant= accepts video only; anything else (thumbnail, spritesheet, …) returns 501 variant_not_supported rather than quietly handing you the main file.

Image-to-video

input_reference is an object, exactly as in the spec — image_url accepts a public URL or a base64 data: URL (10 MB limit; data: URLs are decoded locally). file_id returns 400, since there is no Files API here. A bare string returns 400 rather than being reinterpreted. All three video models accept input_reference — there is currently no video model on this gateway that takes a prompt but refuses a first frame. Ltxv_13B_0_9_8_Distilled_FP8 is by far the cheapest of them and used to reject input_reference even though it supports the mode natively. If you built around Ltx2_3_22B_Dist_INT8 or MiniMaxH3_33B_Turbo_INT8 only because of that rejection, it is worth re-measuring. The reference is fetched while the request is being validated, so an unreachable or oversized address fails the call with 400 input_reference_unreadable and the message carries the status we got. A data: URL is decoded locally and never leaves the gateway.

Fields not to take literally

  • progress is mostly synthetic: 0 when queued, 100 when completed, 50 in between. It is not a render progress meter.
  • expires_at is created_at + 24 h computed on our side. /content re-resolves a fresh download link on every call, so the window is real — but it is our promise, not a backend guarantee.
  • model, prompt, size, seconds, created_at in the GET response come from in-memory state recorded when the job was created. After a gateway restart they come back empty; /content keeps working.
  • An unrecognised backend status is reported as in_progress, so your polling loop keeps going instead of failing on a transient mismatch.
Enum mismatch — read before integrating. The OpenAI spec allows seconds ∈ {4, 8, 12} and four fixed resolutions. Our models publish 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 seconds and eight sizes (512x512, 512x768, 768x512, 768x768, 768x1024, 1024x768, 1024x1024, 1344x768).A strictly typed TypeScript client will not compile "512x512", and Python type checkers will warn. This is the direct consequence of exposing our own models with their own parameters; it cannot be “fixed” without pretending to be OpenAI models. Cast or bypass the enum on the client side.

Errors

Every error under /v1/* uses the OpenAI envelope:
Build your handling on code, not on message. param and code are omitted entirely when they do not apply, so treat them as optional keys rather than expecting empty strings. Some responses — notably 401 for a missing header and every 429 — carry no code at all; for those, type plus the HTTP status is the stable signal. not_implemented and timeout_error are our extensions to OpenAI’s vocabulary: the first means “this surface will not work this way”, the second “retry with a longer deadline”. Unimplemented paths are 404 endpoint_not_implemented, not 501 — 501 is reserved for a recognised route asked for something it cannot produce, which today is only variant_not_supported. 429 carries two different meanings and type tells them apart: rate_limit_exceeded means too fast, insufficient_quota means out of credit. The second is not solved by waiting.

Common codes

Endpoint-specific codes are listed in the sections above.

Retry guidance

  • 503 upstream_unavailable — retry with backoff; this is our capacity, not your payload.
  • 504 upstream_timeout — the job exceeded the wait window; retry, ideally with an Idempotency-Key so you are not billed twice.
  • 502 upstream_job_failed — do not blindly retry; read message first.
  • 429 rate_limit_exceeded — retry after the delay we give you; see Rate limits.
  • 429 insufficient_quota — do not retry; top up the account first. You do not have to code around this: the response carries X-Should-Retry: false, which both official SDKs honour ahead of the status, so it fails once instead of three times.
  • 404 — never retried by the SDKs, which is the point: a wrong path or an unknown model fails immediately instead of costing you four round-trips.

Request IDs

Every response, including errors, carries an X-Request-Id header. Quote it in support tickets — it is how we find your request in the logs. Request bodies are never logged. Endpoints that enqueue work also return X-DeAPI-Request-Id with the backend job identifier — one value for single-job endpoints (audio/speech, audio/transcriptions, videos), or a comma-separated list where the request fans out (embeddings, images/generations, images/edits). It is set before the status code, so it is present on error responses too.

Rate limits

Rate limiting is enforced by your deAPI plan and passed through unmodified. On 429 you get the OpenAI envelope:
Note there is no code field on this one — type is the stable signal. Not every 429 is a rate limit. Running out of credit also returns 429, with "type": "insufficient_quota" and a matching code. Waiting does not clear that one — only a top-up does — so it is sent with X-Should-Retry: false and your SDK stops after the first attempt:
A 429 carries the accounting headers:
The SDKs read Retry-After and x-should-retry only — the X-RateLimit-* values are there for your dashboards, not for the SDK. For the per-plan numbers, see Limits & Quotas. A per-minute limit is deliberately left retryable: waiting is the right response and Retry-After says how long. X-Should-Retry: false is sent on the two 429s where retrying cannot work:
  • insufficient_quota — no amount of waiting changes that answer.
  • A rate limit whose Retry-After exceeds 120 seconds, i.e. the daily window. Neither SDK waits that long unaided: openai-python declines to retry above 120 s, and openai-node honours Retry-After only below 60 s and otherwise falls back to a 0.5 s backoff — three requests into a window that reopens hours later, each one another hit on the same counter.
The header is withheld when the delay is something your client can sensibly wait out, including a daily limit hit just before the window rolls over. The real Retry-After is always sent, so you can schedule your own retry either way.
You cannot track headroom from a successful response here. The native API returns X-RateLimit-Limit and X-RateLimit-Remaining on 200s, so you can throttle before you are throttled. This gateway does not: a successful /v1/* response carries X-Request-Id and nothing else about limits (measured over 351 consecutive calls — no X-RateLimit-* header on any of them). If you need to watch the counter, read it from the native API; here, handle the 429 when it arrives.

Idempotency

Send Idempotency-Key: <uuid> on a POST and a repeated request with the same key and the same body returns the stored response plus Idempotent-Replayed: true — no second job, no second charge.
What you should know about the mechanics:
  • POST only. GET/HEAD and requests without the header pass through untouched.
  • Scoped per caller and per endpoint. The same key on /v1/embeddings and /v1/images/generations are two independent entries, and two accounts never share one.
  • Rotating your token invalidates in-flight keys.
  • Only 2xx responses are stored. After a 4xx you are free to fix the request and retry with the same key instead of being served a stale error.
  • Same key, different body → 422 idempotency_key_reuse. We never quietly serve the previous response.
  • Bodies over 1 MiB bypass the mechanism entirely — in practice, multipart uploads (images/edits, audio/transcriptions). Responses over 1 MiB are not stored either: the request runs normally, it simply cannot be replayed.
  • Two concurrent requests with the same key both execute. There is no in-flight lock; the last one to finish wins. Sequential SDK retries — the common case — are protected.
  • A replayed response carries a fresh X-Request-Id, not the original one.
  • POST /v1/videos is covered too. A retried create would otherwise produce a second job whose id you never learn, orphaned and billed. With a key, the same id comes back.
Entries live for one hour.

Framework integrations

Because deAPI uses the OpenAI API format, it works with any framework that accepts a base_url / api_base parameter.
If your codebase already uses the standard OpenAI environment variables, override them at the process level — zero code changes required:
For multi-provider setups (e.g. OpenAI for chat, deAPI for images and audio), instantiate separate clients:

Deliberate differences from OpenAI

A single list of everything that will not behave the way the OpenAI docs describe.
A field we cannot honour gets an answer, not silence. Where dropping a field would change the response shape or the product you pay for, we return 400 instead of accepting the request. Where a field is a best-effort hint (and is one at OpenAI too), we accept it and say plainly that it does not change the result. That rule is why several fields that used to pass silently now return 400 — you were paying for requests that did something other than what you asked.

Not available yet

deAPI also runs models with no OpenAI counterpart at all — image and video upscaling, background removal, music generation, OCR, character replacement in video. Those stay on the native deAPI API rather than being forced into a foreign contract.

Recent changes

Changed — unimplemented paths answer 404, not 501

Every path this gateway does not serve — GET /v1/videos, DELETE /v1/videos/{id}, /v1/videos/edits, /extensions, /{id}/remix, /characters, /v1/chat/completions, /v1/responses, /v1/files, /v1/moderations, /v1/audio/translations, /v1/images/variations, and anything unrecognised under /v1/* — now answers 404 with code: "endpoint_not_implemented". It used to answer 501. If you branched on the status code, that branch is now dead. Branch on code instead: it is the part that distinguishes “this gateway does not serve this path” from an ordinary 404 such as model_not_found or video_not_found. 501 survives in one place only — variant_not_supported on /v1/videos/{id}/content. model_not_found moved the same way: 404 on POST endpoints too, where it used to be 400. One code on two statuses forced every client to handle both. The reason for the whole move is in Supported endpoints: both SDKs retry any status >= 500, so the old 501 turned a typo into four requests and an InternalServerError.

Added

  • GET /v1/models/{model} — client.models.retrieve(...) now works instead of refusing the path.
  • Speaker diarization — response_format="diarized_json" on WhisperLargeV3Ct2, with sequential speaker labels.
  • Native word-level timestamps — timestamp_granularities=["word"] returns the model’s own word timings on WhisperLargeV3Ct2, instead of interpolating them across the segment. Models that publish no word-level support reject the field outright rather than handing back invented offsets.
  • instructions for text-to-speech — describe a voice in words on Qwen3_TTS_12Hz_1_7B_VoiceDesign.
  • application/json on /v1/images/edits — the second content type the spec defines; images are passed as images: [{"image_url": …}].
  • New models: ZAnimeDistill_8Step_INT8, WhisperLargeV3Ct2, MiniMaxH3_33B_Turbo_INT8, Qwen3_TTS_12Hz_1_7B_VoiceDesign.
  • Browser support — /v1/* now sends CORS headers, so the official Node SDK runs in the browser with dangerouslyAllowBrowser: true. Preflight no longer fails on authentication, which previously made every browser-side call impossible. Read the warning before you ship it.
  • created is populated for every model in GET /v1/models. It reports when the model became available here; entries used to be 0 unless the model was also listed on OpenRouter.
  • Image-to-video on Ltxv_13B_0_9_8_Distilled_FP8 — the model supports it natively and we were rejecting it. It is the cheapest image-to-video model in the catalog by a wide margin.

Fixed

  • Image-to-video now works. input_reference used to be silently dropped: the job succeeded, you were billed, and the clip was generated from the prompt alone. It is now an object, routed to the correct backend endpoint.
  • Models no longer disappear from /v1/models. The catalog is paginated and only the first page was read, so correctly configured models were missing from the list with no error.
  • response_format="text" on CTranslate2 models returned a raw JSON document, and verbose_json returned an empty segments array.
  • POST /v1/videos/edits, /extensions and /characters answered 405, telling you the path existed and the method was wrong — both untrue. They now answer 404 endpoint_not_implemented.
  • language in transcription is no longer an echo — it reaches the model and affects the transcript.
  • Running out of credit reported 400 — “your request is malformed”, when the request was fine. It is now 429 insufficient_quota, sent with X-Should-Retry: false so your SDK does not retry an answer only a top-up can change: one failed call instead of three.
  • param named the backend’s field, not yours. A validation error on input, file, input_reference or response_format came back pointing at an internal field name that appears nowhere in your request. The names are now translated back to the ones you sent.
  • Unrecognised paths under /v1/videos/ answered 405. The earlier fix covered only the three collection names spelled out in the spec (/edits, /extensions, /characters); every other word still leaked a 405. The whole class now answers 404.

Changed — may affect existing integrations

  • Ltxv_13B_0_9_8_Distilled_FP8 accepts input_reference. Image-to-video is not limited to the Ltx2_* and MiniMaxH3_* families; all three video models take a first frame.
  • Partner models (gpt-image-1.5, gpt-image-2, nano-banana-2) are not routable here. They are in the native catalog and answer 404 model_not_found on this gateway.
  • upstream_validation_error was renamed to invalid_value — a code from OpenAI’s own vocabulary rather than one of ours.
  • timestamp_granularities=["word"] on a segment-only model now returns 400. It used to return 200 with word timings interpolated evenly across each segment; those numbers looked like measurements and were not.
  • Ltx2_3_22B_Dist_INT8 no longer advertises 2 seconds. The backend requires at least 49 frames and 2 s is 48, so that value always failed; 3 and 5 stand in its place.
Fields that used to be accepted and silently dropped now return 400: stream=true, partial_images, background=transparent, output_format=jpeg|webp, output_compression (both image endpoints); input_fidelity (edits — on /v1/images/generations the spec does not define it and it is accepted and ignored); stream_format=sse (speech); stream=true, include[], languages[], chunking_strategy (transcription).
  • quality="auto" no longer returns 400. It is the SDK’s default value and now maps to the model’s default quality.
  • Job failures return a real reason. Retryable failures come back as 503, permanent ones as 502 with a message — previously every failure was an empty 502.
  • TTS is mp3-only. wav and flac were withdrawn from the TTS family on the backend and are now rejected locally with 400 format_not_supported.

Support

Include the X-Request-Id from the failing response (and X-DeAPI-Request-Id if present) in any report — with those we can trace the request end to end.

Next steps

Models

Discover all available models and their capabilities

Quickstart

Get your API key and make your first request

API v2 Reference

Full native API reference with all endpoints

Pricing

Pay-as-you-go rates per task and model