Subscribe at
/changelog/rss.xml to hear about deprecations and
breaking changes without checking back. Every entry that removes or changes existing
behaviour is tagged Deprecated or Changed; where the change is a response shape,
the entry says which API version carries it — see
API versioning for how to pin a version so a shape
change cannot reach your code unannounced.AddedImagesAudioChangedDeprecated
Mesh Code desktop app; limit a BYOK key to models; fal image models on image edits; clearer audio errors; fixed usage keys on all three inference endpoints; web search priced per engine
Web search is priced per engine
Changed.POST /v1/web/search is billed at the price of the engine that answered,
instead of a flat $0.005: native $0.005, tavily $0.008 ($0.016 with
search_depth: "advanced"), tinyfish $0.005 plus $0.001 per returned result
carrying page_content. Failed and empty searches are still free.
See Web Search billing.Fixed usage keys on Responses and Messages; provider keys return on chat
Changed. POST /v1/responses and POST /v1/messages now return a fixed set of
usage keys whichever provider serves the request, all present and 0 when unused:- Responses:
input_tokens_details(cached_tokens,cache_write_tokens,cache_write_1h_tokens,audio_tokens),output_tokens_details.reasoning_tokens,total_tokens,byok_usedandfallback_triggered. - Messages:
cache_creation_input_tokens,cache_read_input_tokens,cache_creationsplit by TTL,byok_usedandfallback_triggered, on the response body and onmessage_delta. They were previously returned only when non-zero.
is_byok,
prompt_cache_hit_tokens or num_sources_used) are returned alongside the fixed keys.
On chat completions they had been removed since the change below. This applies to every
API version. See Usage Object.One usage shape on chat completions
Changed. usage on POST /v1/chat/completions (including the final chunk of a stream)
now has the same keys whichever provider serves the request, all present and 0 when
unused: prompt_tokens_details (cached_tokens, cache_write_tokens,
cache_write_1h_tokens, audio_tokens), completion_tokens_details, byok_used and
fallback_triggered. total_tokens is always prompt_tokens + completion_tokens. Keys a
provider added on its own, such as is_byok or prompt_cache_hit_tokens, are no longer
returned; a provider’s cache hits appear as cached_tokens. This applies to every API
version. See Reading the result.Deprecated. The top-level usage.cache_creation_input_tokens on Claude responses
duplicates prompt_tokens_details.cache_write_tokens. It is still returned; retirement
date to be announced.Limit a BYOK key to specific models
A provider key you register with Mesh used to serve every model Mesh routes to that provider. Allowed models on the key (allowed_models in the API) now limits it to
exact model IDs or brand wildcards such as openai/*. A model outside the list is served
on Mesh’s credentials and billed to your balance, or, with fallback off, refused with
403 byok_model_not_allowed. See
Limit a key to specific models.Mesh Code
Mesh Code is a desktop app for agentic coding, with every Mesh model behind one key. It plans, writes files, runs commands and starts dev servers inside a project folder you choose; each step is either auto-approved by a mode you pick or shown to you as a diff first. Version 0.2.4 (1 October) ships for macOS (Apple silicon), Linux (AppImage) and Windows (x64). Download it from GitHub.fal image models on /v1/images/edits
fal-ai/gemini-3-pro-image-preview/edit and fal-ai/nano-banana-pro/edit serve
POST /v1/images/edits, with one source image or several. They bill at the model’s
per-image or per-megapixel rate. See
Edit an image.Clearer audio errors
- Gemini speech-to-text accepts
.m4a,.mp4,.webmand.opusaudio, and a file a model cannot take is refused with a message that says why, not a generic capability error. - A rate-limited ElevenLabs speech-to-text request moves on to fallback instead of
failing as
422model_id: Field required.
Fixes
- A
toolmessage whosecontentis a list of parts reaches Vertex Gemini models as its text instead of failing with500. - A multipart upload sent to a JSON endpoint is a
422validation error, not a500. - Large usage exports no longer fail partway through.
AddedChangedDeprecatedAPIVideoAudio
API version 2026-09; video billing; flex tier; evaluate, template versions, end-user erasure; provider stop reasons; new webhook events and models
Poll a video task with the id you already have
Version2026-09 is out (released 3 September). Pin it and
GET /v1/video/generations/{id} accepts the request_id Mesh returned when you created
the task, as well as the provider task id:404,
never a leak.Video task bodies also gained a request_id field — on the create response, on
GET /v1/video/generations/{id}, and on every row of the GET /v1/video/generations
list. Under 2026-08 the field is stripped, so a pinned parser sees exactly what it saw
before.Two things worth knowing before you switch:- Tasks created before 20 August 2026 predate the recorded request id and stay reachable by task id only.
2026-08is still the baseline, so an unpinned request is unaffected. Send the header (or pin it on your key) to use this.
GET /v1/api-versions now reports a capabilities list per version — 2026-09 carries
video.poll_by_request_id — so you can check what a version gives you without reading
the changelog. The list is cumulative: a later version includes everything an earlier one
introduced. See API versioning.Model pricing shows the rate you are billed
Under2026-09, when your account has a discount on a model, its pricing object on
GET /v1/models, GET /v1/models/search and the per-model reads carries discounted:
the same rates with the discount applied. That covers tokens, per-image, per-second
video, resolution tiers, cache reads and price_matrix entries. With no discount the
field is absent. List prices are unchanged. tool_pricing is not included, since a
hosted-tool fee is charged after the discount. Under 2026-08 the field is stripped.Where a discount covers only some of the provider accounts that can serve a model,
discounted shows the best rate attainable; a request bills at the rate of the account
that served it. A request served on your own provider key (BYOK) is billed without the
discount.Video billing follows what the provider renders
Changed. These video routes now bill the clip the provider renders:- DeepInfra
google/veo-3.1andgoogle/veo-3.1-fastbill their fixed 8 s clip, not 5 s. - AtlasCloud video bills per second at the resolution tier.
alibaba/wan-2.7is 0.15/s at 1080P or with no resolution sent. Most other AtlasCloud video models are re-priced per second from AtlasCloud’s own quotes, with a separate audio rate where AtlasCloud charges one. A request that leaves out duration, resolution or audio bills what AtlasCloud renders by default. - Runware video bills its sound rate whenever sound is rendered, including when the
model adds it by default (
google/veo-3.1does unless you turn it off). - Venice video bills Venice’s quote for the duration, resolution and audio you ask for, not one flat price.
- A BytePlus Seedance request with no
resolutionbills the tier it renders by default (720p for Seedance 2.x and 1.5 pro, 1080p for 1.0 pro). It used to bill a higher rate.
video-edit, Grok
edit-video, and the -SR super-resolution tiers).A video input a model cannot take is now a 422
Send a content item whose role the serving model does not accept — a closing frame to
a model with no concept of one, a reference video to a model that takes only images —
and the submit is now rejected with 422. The message names the item by its position
in content and, where the item was reinterpreted on the way in, what it resolved to.
It used to be dropped: the generation ran without it and you were billed for a video
built from inputs you did not send.For example, bytedance/seedance-1.0-lite takes an opening frame but not a closing one,
so a second image sent as the last_frame is refused:unsupported_params — a dropped parameter still gives you the
video you asked for, tuned differently, while a dropped asset changes what was generated.This applies on every API version. It is a refusal, not a response shape, so pinning
X-Mesh-Version does not hold the old behaviour.Check supports_video_first_frame and its five siblings on GET /v1/models before you
send, and GET /v1/models/{model_id}/providers to see whether any route takes the input.Full detail: Finding out what a model accepts.Error messages name the provider that answered
Changed. An error from an upstream provider now keeps that provider’s name inmessage, upstream_error and provider_error.provider. On our own provider keys a
cloud or reseller provider’s name (OpenRouter, Bedrock, Vertex, BytePlus, …) used to
read “the provider”; your own keys (BYOK) always showed it. Only the name is new: cloud resource ids and anything credential-shaped
are still removed, and a problem with our own provider account still reads as a
generic upstream_error. The name describes who answered that one request, not a
routing guarantee. See What errors never contain.Stop reasons arrive as the provider sent them
Changed.finish_reason is mapped to an OpenAI value only where the meaning is the
same (end_turn → stop, content_filtered → content_filter) and is forwarded
verbatim otherwise, so a Gemini MALFORMED_FUNCTION_CALL or an Anthropic pause_turn
no longer reads as stop. Every chat choice also carries native_finish_reason, the
provider’s untranslated value, or null when the provider sent none. This applies on
every API version, streamed or not.Evaluate: typed answers instead of prose
POST /v1/evaluate asks a model structured questions about a piece of application state
and returns each answer typed: a chosen option with a probability for every option, a
score on a scale you define, or a single probability. It is served by
typesafe/jev-1.13 and is in beta, so the contract may change. A key restricted to
specific scopes needs evaluate:write. The same call is the evaluate tool on the
MCP server. See Evaluate.Prompt templates keep a version history
Template content is stored as immutable versions. Saving a version leaves the live one alone; publishing switches it:POST /v1/templates/{id}/versionssaves a version;GET .../versionsandGET .../versions/{version_id}read them.POST /v1/templates/{id}/publishmakes the latest saved version live.POST /v1/templates/{id}/versions/{version_id}/rollbackrepublishes an earlier version’s content as a new version.- A chat request can set
template_versionto run an exact version instead of the published one.
PATCH /v1/templates/{id}: system, messages,
model, params and variables. They are still accepted, and a PATCH that carries
them saves a new version and publishes it immediately. No retirement date is set. See
Prompt templates.Erase an end user’s identifier
POST /v1/end-users/erasures takes {"end_user_id": "..."}, the identifier you sent as
safety_identifier or user, and clears it from your organization’s usage records, so
a deletion request from one of your own users can be passed through. Billing rows are
kept; only the identifier is removed. Request logs are not erased, and pending always
lists them. It also lists usage records when one call hits its row cap; call again to
finish. The analytics copy is not erased either; it expires within 90 days. A request still in
flight can re-attribute a row, so call again once traffic for that identifier has
stopped; the call is idempotent. The key must belong to an organization; a key
restricted to specific scopes needs end-users:write.safety_identifier and the reasoning object
Deprecated: user, in favour of safety_identifier, the name OpenAI moved the field
to. Both are accepted on chat completions, Responses and embeddings, and
safety_identifier wins when both are sent. safety_identifier is also accepted on
images, audio, video, batch, compare, moderations, web search, file search and
/v1/messages. The dashboard’s usage CSV export can be filtered to one end user by the
exact safety_identifier sent.Deprecated: reasoning_effort on /v1/chat/completions, in favour of an
OpenRouter-compatible reasoning object. Send reasoning.effort (max, xhigh,
high, medium, low, minimal or none) or reasoning.max_tokens, a thinking
budget, but not both. When the object and reasoning_effort are both sent, the object
wins. OpenRouter receives the object as sent. Other providers get the effort it implies
where they take one, with a max_tokens budget mapped to low, medium or high.
exclude: true (reason without returning the reasoning) is honoured only on OpenRouter
and on OpenAI models served through the Responses API.Neither deprecated field has a retirement date yet.Flex service tier
service_tier: "flex" on /v1/chat/completions and /v1/responses serves an OpenAI
model from OpenAI’s slower, cheaper flex pool and bills it at OpenAI’s flex rates. A
model that cannot serve flex is refused rather than billed at standard rates. It
launched on the OpenAI models with published flex prices, and openai/gpt-6-sol and
openai/gpt-6-luna joined later in the month. See
Service Tiers.Changed. service_tier takes only auto, default and flex. priority, fast
and scale used to be accepted and dropped; they are now a 422.Model discovery
GET /v1/models/searchtakescapability=(for exampletools,thinking,image_edit), returnsfacetscounting the results each filter value would give, and sorts withsort=pricewhen paired with exactly oneoutput_modalityoftext,imageorvideo.- Discovery follows the calling key’s model policy:
GET /v1/models,/v1/models/searchand the per-model reads list only models the key may call, and a per-model read of an excluded model is403. - Changed.
supports_batching, on/v1/modelsand/v1/models/{id}/providersand in thebatchingfilter, istrueonly where/v1/batchescan run the model. A model whose default provider is Anthropic, Vertex or Bedrock now readsfalse;/v1/batchesanswered501for it.
Exclude models from a key, an organization or a team
An API key can carryblocked_models beside allowed_models: exact model ids or brand
wildcards it may not call. Exclusion wins over the allow-list, and the Auto Router,
fallbacks and /v1/router/select respect it. A refused request is 403 model_excluded,
naming the model. Set it in the key drawers under API Keys, or on /v1/keys with an
admin key. Model access can also be set for the organization, a team or a member in the
dashboard; every layer has to permit a model. See
API keys.Pin a key to one provider
fixed_provider on POST and PATCH /v1/keys makes one provider the only one that may
serve that key. There is no cross-provider fallback, and a model that provider does not
serve is 400 model_not_available_on_pinned_provider. GET /v1/providers/pinnable
lists the providers you can pin to, and GET /v1/providers/{provider}/models lists what
a pin still serves.Key routing policy and rate-limit tiers
- Changed. A saved key routing policy stores only the fields you set, so an omitted
field follows the gateway default. A policy saved before this change keeps what it
stored, which can include
fallback.enabled: falseon a key whose owner only turned on retries. See Per-key routing policy. - Your organization’s rate-limit tier sets how far its admins can raise a key’s
rpm_limitandrpd_limit.standardallows 1,000 rpm and 1,000,000 rpd, as before;enterpriseallows 20,000 and 5,000,000. Mesh sets the tier; ask support to change it.
New webhook events
payment.failed: a Stripe or Cashfree top-up failed, or an auto-recharge charge failed (which also firesauto_recharge.failed). Your balance is unchanged.byok_key.failed: one of your provider keys was rejected upstream; the payload says whether the request was served on Mesh credentials instead.provider.fallback: a request was served by a different provider than the one requested. It is sent at most once every 5 minutes for each pair of providers and model.
New models
- Gemini audio:
google/gemini-3.5-transcribe(speech-to-text) andgoogle/gemini-2.5-flash-ttson Vertex;google/gemini-3.1-flash-tts, already on Venice, now also on Vertex as a fallback;google/gemini-3.8-flash-ttsandgoogle/gemini-3.8-flash-lite-ttson AI Studio. See Text to Speech and Speech to Text. - ElevenLabs:
elevenlabs/eleven_v4andelevenlabs/eleven_v4_turbotext-to-speech. - GPT Image 2.5:
openai/gpt-image-2.5-flareandopenai/gpt-image-2.5-sunburst, with edits, inpainting and reference images. See Image Generation. - Sync Lipsync v3 on fal:
fal-ai/sync-lipsync/v3lipsyncs a video to an audio track, andfal-ai/sync-lipsync/v3/image-to-videoanimates a still image. See Video Generation.
Pin the API contract to a date
Mesh now versions its public contract by date, in a request header. The current version is2026-08 (released 15 August):/v1 stays exactly where it is — it is a namespace, not a
version, and there is no /v2 planned.You do not have to do anything. Sending no header gets you the oldest supported
version, so publishing a new one never moves existing traffic.GET /v1/api-versionslists every version served, with itsstatus, release date, and which one is thebaselineand which islatest.- An API key can carry a pin, so code you do not control still gets a fixed contract — set it on the key’s API Version field under API Keys. A header on the request wins over the key’s pin.
- Every response echoes
X-Mesh-Versionwith the version that served it. - A version Mesh does not serve is rejected with
400 invalid_api_versionrather than silently downgraded, so a typo’d pin fails immediately instead of quietly serving you something else. The error body listssupported_versions.
Deprecation and
Sunset headers first, so monitoring can notice before anyone reads a page. Nothing is
deprecated today.Full detail: API versioning.Anthropic Messages API
POST /v1/messages speaks Anthropic’s Messages format, so a client built on Anthropic’s
SDK, Claude Code included, can point at Mesh unchanged
and address any model Mesh serves, not only Claude. Streaming, tool use and vision are
supported. See Messages API.Playground saves conversations
The dashboard’s chat Playground saves each conversation as a thread, together with the request configuration that produced it, so reopening a thread restores the run. Threads are visible only to you.Node.js SDK 2.0.0 — the request id rides on what each call returns
Breaking:MeshAPIConfig.onResponse and the ResponseInfo type are gone. The hook
was configured once per client, so with several calls in flight it could not say which
response belonged to which call.The id now lives on the value the call returned:- Every streaming method returns an
SSEStream<T>, anAsyncIterablethat also carriesrequestIdas a promise. It stays readable after a mid-stream failure or an abort, which is when you most want it. SSEStream.cancel()releases the connection for a stream you read the id from but never iterated. It is also wired toSymbol.asyncDisposeforawait using.- Breaking out of a
for awaitover a stream now closes the connection; previously it leaked the socket. - Iterating one stream object twice no longer issues, and bills, a second request.
- Errors raised from a mid-stream error frame no longer report an empty
requestId.
onResponse has to
change. See Streaming and Error handling.SDK releases
- Node.js 2.0.0 — request id on every returned value;
onResponseremoved (above). - Node.js 1.0.4 — repaired Responses API streaming and the realtime ESM entry point;
clearer error messages;
models.free()/models.paid()now filter server-side via?free=; the SDK version header matches the package version again. - Node.js 0.1.2 — exposed
x-request-idon successful responses viaonResponse(superseded by 2.0.0 four days later). Taggedv1.0.2in the repository.
Translate audio to English
POST /v1/audio/translations accepts audio in any language and returns the speech
translated to English. This is distinct from the transcribe-and-translate helper at
POST /v1/audio/transcriptions/translate, which transcribes first and then translates
the text.model is required — check GET /v1/models for models that support translation. A
model that does not is refused with 422. See Audio.Structured outputs in the SDKs
Node.js 0.1.1 adds aparse() method on chat completions that sends a JSON-schema
response_format and returns a typed value instead of a string:parse() is not yet published to PyPI. See
Structured output.Realtime audio — GA protocol
Python 0.1.11 and Go v0.1.12 move the realtime client to the GA wire protocol: audio is sent as base64input_audio_buffer.append events, and output audio is decoded into the
message’s audio field rather than left as a base64 string. See
Realtime.SDK releases
- Python 0.1.11 — realtime GA protocol.
- Python 0.1.10 — realtime TLS trusts the
certifibundle; stricter request params; error mapping matches the gateway’s error codes;image_bytes()helper for reading generated images; spec fields that had been dropped are now declared. - Python 0.1.9 — transcription
language_codeexample fixed. - Python 0.1.8 — audio translations.
- Go v0.1.12 — realtime GA protocol.
- Go v0.1.11 — SSE buffer no longer truncates large frames; error mapping matches the
gateway’s error codes;
Bytes()helper for generated images; missing spec fields added. - Go v0.1.10 — transcription
language_codeexample fixed. - Go v0.1.9 — audio translations.
- Node.js 0.1.1 — covers the full public data plane: moderations, web search, router
select, models search and get, Responses API list and get, image edits, audio
translations, plus structured outputs. The unreleased
/v1/documentswrapper was removed.
meshapi), npm (meshapi-node-sdk) and the Go module
proxy. Before 1.0.4, Node.js repository tags do not match npm: npm 0.1.1 is tagged
v1.0.0 and v1.0.1, and 0.1.2 is v1.0.2. See SDK overview for
install commands.