Skip to main content
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_used and fallback_triggered.
  • Messages: cache_creation_input_tokens, cache_read_input_tokens, cache_creation split by TTL, byok_used and fallback_triggered, on the response body and on message_delta. They were previously returned only when non-zero.
On all three endpoints, keys a provider adds on its own (such as 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, .webm and .opus audio, 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 422 model_id: Field required.

Fixes

  • A tool message whose content is a list of parts reaches Vertex Gemini models as its text instead of failing with 500.
  • A multipart upload sent to a JSON endpoint is a 422 validation error, not a 500.
  • 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

Version 2026-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:
Useful when the request id is what your logs and your database already hold, so you no longer have to store a second identifier just to check on a video. The task id is tried first and the request id only when it misses, so nothing you poll today changes. Both lookups are scoped to the key’s owner: an id that belongs to someone else is a 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-08 is 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

Under 2026-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.1 and google/veo-3.1-fast bill their fixed 8 s clip, not 5 s.
  • AtlasCloud video bills per second at the resolution tier. alibaba/wan-2.7 is 0.10/sat720Pand0.10/s at 720P and 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.1 does 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 resolution bills 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.
Two requests are now refused at submit instead of billed at the wrong rate: sound on a Runware model that prices sound separately but has no audio rate for that clip, and an AtlasCloud per-second request no rate covers (Kling o3 and Omni 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:
Top-level parameters are unchanged. A tunable the model does not implement is still accepted and reported in 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 in message, 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}/versions saves a version; GET .../versions and GET .../versions/{version_id} read them.
  • POST /v1/templates/{id}/publish makes the latest saved version live.
  • POST /v1/templates/{id}/versions/{version_id}/rollback republishes an earlier version’s content as a new version.
  • A chat request can set template_version to run an exact version instead of the published one.
Deprecated: the content fields on 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/search takes capability= (for example tools, thinking, image_edit), returns facets counting the results each filter value would give, and sorts with sort=price when paired with exactly one output_modality of text, image or video.
  • Discovery follows the calling key’s model policy: GET /v1/models, /v1/models/search and the per-model reads list only models the key may call, and a per-model read of an excluded model is 403.
  • Changed. supports_batching, on /v1/models and /v1/models/{id}/providers and in the batching filter, is true only where /v1/batches can run the model. A model whose default provider is Anthropic, Vertex or Bedrock now reads false; /v1/batches answered 501 for it.
See Models.

Exclude models from a key, an organization or a team

An API key can carry blocked_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: false on 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_limit and rpd_limit. standard allows 1,000 rpm and 1,000,000 rpd, as before; enterprise allows 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 fires auto_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.
See Notifications.

New models

  • Gemini audio: google/gemini-3.5-transcribe (speech-to-text) and google/gemini-2.5-flash-tts on Vertex; google/gemini-3.1-flash-tts, already on Venice, now also on Vertex as a fallback; google/gemini-3.8-flash-tts and google/gemini-3.8-flash-lite-tts on AI Studio. See Text to Speech and Speech to Text.
  • ElevenLabs: elevenlabs/eleven_v4 and elevenlabs/eleven_v4_turbo text-to-speech.
  • GPT Image 2.5: openai/gpt-image-2.5-flare and openai/gpt-image-2.5-sunburst, with edits, inpainting and reference images. See Image Generation.
  • Sync Lipsync v3 on fal: fal-ai/sync-lipsync/v3 lipsyncs a video to an audio track, and fal-ai/sync-lipsync/v3/image-to-video animates a still image. See Video Generation.
AddedChangedAPISDKs
Dated API versions; Anthropic Messages API; Node.js SDK 2.0

Pin the API contract to a date

Mesh now versions its public contract by date, in a request header. The current version is 2026-08 (released 15 August):
A pinned request states which response shapes your code can parse, so a future change to those shapes cannot reach it. /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-versions lists every version served, with its status, release date, and which one is the baseline and which is latest.
  • 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-Version with the version that served it.
  • A version Mesh does not serve is rejected with 400 invalid_api_version rather than silently downgraded, so a typo’d pin fails immediately instead of quietly serving you something else. The error body lists supported_versions.
When a version is eventually retired, its responses will carry standard 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>, an AsyncIterable that also carries requestId as 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 to Symbol.asyncDispose for await using.
  • Breaking out of a for await over 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.
Widening the return types is source-compatible: only code that set onResponse has to change. See Streaming and Error handling.

SDK releases

  • Node.js 2.0.0 — request id on every returned value; onResponse removed (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-id on successful responses via onResponse (superseded by 2.0.0 four days later). Tagged v1.0.2 in the repository.
AddedAudioSDKs
Audio translations, structured outputs, realtime GA protocol, SDK releases

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 a parse() method on chat completions that sends a JSON-schema response_format and returns a typed value instead of a string:
It takes any Standard Schema validator (Zod, Valibot, ArkType) or a raw schema. The Python 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 base64 input_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 certifi bundle; 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_code example 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_code example 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/documents wrapper was removed.
Versions are as published to PyPI (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.