<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Modelflare</title>
    <description>The latest articles on DEV Community by Modelflare (modelflare_dev).</description>
    <link>https://dev.to/modelflare_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15266%2F39c052ca-a92a-464e-a2dc-082945184e5e.png</url>
      <title>DEV Community: Modelflare</title>
      <link>https://dev.to/modelflare_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/modelflare_dev"/>
    <language>en</language>
    <item>
      <title>Why AI Agent Cache Hit Rates Collapse: GPT, Claude, and Auditable Gateways</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Thu, 29 Oct 2026 02:10:06 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/why-ai-agent-cache-hit-rates-collapse-gpt-claude-and-auditable-gateways-1ian</link>
      <guid>https://dev.to/modelflare_dev/why-ai-agent-cache-hit-rates-collapse-gpt-claude-and-auditable-gateways-1ian</guid>
      <description>&lt;p&gt;A source-backed guide to third-party agent cache failures, GPT and Claude prompt-caching mechanics, reproducible hit-rate measurement, and Modelflare's public compensation boundary.&lt;/p&gt;

&lt;p&gt;AI agent caching is not a checkbox. It is a protocol, routing, and accounting contract. This article explains why an opaque third-party agent can show a very low cache hit rate even when the upstream model supports prompt caching, how GPT and Claude measure reuse differently, and what an auditable gateway should guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision in one sentence
&lt;/h2&gt;

&lt;p&gt;Do not put a production, cache-sensitive agent behind a third-party agent or gateway that cannot expose native cache usage, preserve a stable prefix, keep model and route affinity, and reconcile the result against a durable ledger. “Supports caching” is a feature claim; a measured, eligible hit rate is an operating fact.&lt;/p&gt;

&lt;p&gt;This is not a claim that every third-party agent fails. Public documentation cannot establish a vendor-wide percentage without a fixed corpus, a fixed model, a known observation window, and request-level usage evidence. The reliable conclusion is narrower: an opaque translation layer creates several independent ways to turn a valid provider cache into an almost-always-cold path. Treat that lack of evidence as a reason not to use the path for cache-dependent production work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the denominator before discussing a percentage
&lt;/h2&gt;

&lt;p&gt;The provider usually reports three different buckets. Let &lt;code&gt;R&lt;/code&gt; be tokens read from a cache, &lt;code&gt;W&lt;/code&gt; be tokens written to a cache, and &lt;code&gt;U&lt;/code&gt; be input tokens processed without reuse. For cache-eligible prefixes, the comparable hit rate is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eligible_hit_rate = R / (R + W)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For all input sent to the model, the reuse share is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;input_reuse_share = R / (R + W + U)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Those numbers answer different questions. A dashboard that divides by all input can look low when the agent sends many short, ineligible requests. A dashboard that divides only eligible traffic can look high while a large dynamic suffix still dominates spend. Report both, plus the eligibility rule and the time window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fai-agent-cache-prefix.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fai-agent-cache-prefix.svg" alt="A stable prefix enters the reusable cache while changing agent state bypasses it" width="1200" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The provider fields are the evidence, not a green badge in an agent UI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;OpenAI field&lt;/th&gt;
&lt;th&gt;Claude field&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reused prefix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input_tokens_details.cached_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cache_read_input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens actually served from a matching entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New cache entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input_tokens_details.cache_write_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cache_creation_input_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokens charged to create or extend an entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreused input&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;input_tokens - input_tokens_details.cached_tokens - input_tokens_details.cache_write_tokens&lt;/code&gt; (derive carefully)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;input_tokens&lt;/code&gt; after the breakpoint&lt;/td&gt;
&lt;td&gt;Input outside the cached prefix; semantics differ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correlation&lt;/td&gt;
&lt;td&gt;request ID plus model and route&lt;/td&gt;
&lt;td&gt;request ID plus model and route&lt;/td&gt;
&lt;td&gt;Which provider attempt produced the usage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Worked example, using synthetic numbers rather than Modelflare production telemetry: &lt;code&gt;R=720,000&lt;/code&gt;, &lt;code&gt;W=80,000&lt;/code&gt;, and &lt;code&gt;U=200,000&lt;/code&gt; give an eligible hit rate of &lt;code&gt;720,000 / 800,000 = 90%&lt;/code&gt;, but an all-input reuse share of &lt;code&gt;720,000 / 1,000,000 = 72%&lt;/code&gt;. Publishing only one of those values hides the denominator.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;input_tokens_details.cache_write_tokens=0&lt;/code&gt; is not proof of “no cache.” A request can be a cache read and therefore create no new entry. Conversely, a missing usage field in a third-party response is not a zero; it is an observability failure that must be recorded as &lt;strong&gt;not auditable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an agent gateway loses hits even when the model supports caching
&lt;/h2&gt;

&lt;p&gt;The failure is usually in the path between the application and the provider. These are the high-frequency breakpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic bytes arrive too early.&lt;/strong&gt; Timestamps, request IDs, user names, experiment flags, or a changing “current date” in the system prompt alter the prefix before the reusable instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools are reserialized.&lt;/strong&gt; Adding, removing, reordering, or nondeterministically serializing a tool schema changes the exact prefix. A harmless-looking JSON key order can become a cache miss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback changes the cache identity.&lt;/strong&gt; A load balancer that moves between model aliases, regions, organizations, or provider credentials does not share one universal cache entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The adapter drops native controls.&lt;/strong&gt; Removing &lt;code&gt;cache_control&lt;/code&gt;, &lt;code&gt;prompt_cache_key&lt;/code&gt;, retention options, or usage details turns a provider feature into an invisible best effort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The prompt is below the provider threshold.&lt;/strong&gt; Short agent turns can be valid requests but still be ineligible for caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The TTL window is crossed.&lt;/strong&gt; A five-minute Claude entry or a model-specific OpenAI retention window can expire during a human-in-the-loop pause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel warm-up races.&lt;/strong&gt; Concurrent first requests may all arrive before the first response makes a cache entry available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History is rewritten.&lt;/strong&gt; Summarization, compaction, truncation, or a different serialization of prior messages changes the prefix instead of appending to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fcache-hit-rate-denominator.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fcache-hit-rate-denominator.svg" alt="Three request shapes show how reads, writes, and uncached input create different denominators" width="1200" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;None of these failures requires a dishonest vendor. They are predictable consequences of a gateway that treats an agent request as free-form text instead of preserving a provider’s cache contract. The practical warning is stronger than a marketing comparison: if the agent cannot show which breakpoint failed, you cannot price or debug cache-dependent traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT and Claude use related ideas, not interchangeable semantics
&lt;/h2&gt;

&lt;p&gt;Both providers require an exact reusable prefix, but the control surface and usage accounting are different. The table below summarizes the official guides checked on &lt;strong&gt;2026-08-25&lt;/strong&gt;; model names, minimums, and retention policies can change, so recheck the linked pages before changing a production policy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;OpenAI prompt caching&lt;/th&gt;
&lt;th&gt;Claude prompt caching&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reusable unit&lt;/td&gt;
&lt;td&gt;The full rendered context prefix, including instructions, tools, history, and multimodal parts&lt;/td&gt;
&lt;td&gt;The ordered prefix through a &lt;code&gt;cache_control&lt;/code&gt; breakpoint: tools, system, then messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum length&lt;/td&gt;
&lt;td&gt;The current guide lists 1,024 visible tokens for GPT-5.6+ and generally 2,048 for older models&lt;/td&gt;
&lt;td&gt;Model-specific minimums currently span roughly 512–4,096 tokens; shorter prompts run without caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controls&lt;/td&gt;
&lt;td&gt;Implicit caching; explicit breakpoints and a stable &lt;code&gt;prompt_cache_key&lt;/code&gt; on supported models&lt;/td&gt;
&lt;td&gt;Top-level automatic caching or explicit block breakpoints; up to four breakpoints and a 20-block lookback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention&lt;/td&gt;
&lt;td&gt;GPT-5.6+ supports a 30-minute TTL; earlier models expose retention modes with provider-defined typical windows&lt;/td&gt;
&lt;td&gt;Five-minute default TTL, refreshed on use; optional one-hour TTL at a higher write price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price shape&lt;/td&gt;
&lt;td&gt;On the current GPT-5.6+ guide, writes are 1.25× base input and reads are 0.1×&lt;/td&gt;
&lt;td&gt;Five-minute writes are 1.25× and one-hour writes are 2×; reads are 0.1×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage evidence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;input_tokens_details.cached_tokens&lt;/code&gt; and, when applicable, &lt;code&gt;input_tokens_details.cache_write_tokens&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cache_read_input_tokens&lt;/code&gt;, &lt;code&gt;cache_creation_input_tokens&lt;/code&gt;, and post-breakpoint &lt;code&gt;input_tokens&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frequent invalidator&lt;/td&gt;
&lt;td&gt;Model/tool/settings changes, machine overflow, or a prefix change before the breakpoint&lt;/td&gt;
&lt;td&gt;Model/system/tools/message changes, a missing previous message, or a concurrent warm-up miss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OpenAI notes that cache entries live on individual machines and that a &lt;code&gt;prompt_cache_key&lt;/code&gt; helps group and route requests but does not pin a machine or guarantee a hit. Claude documents exact matching, workspace-level isolation, and explicit diagnostics such as &lt;code&gt;tools_changed&lt;/code&gt; and &lt;code&gt;messages_changed&lt;/code&gt;. A gateway that hides those distinctions cannot honestly convert provider documentation into a single “cache rate” promise.&lt;/p&gt;

&lt;p&gt;The price multipliers explain why this matters. If one write is followed by one read, the cached input portion costs about &lt;code&gt;1.25 + 0.10 = 1.35×&lt;/code&gt; the uncached input unit on the cited OpenAI model, rather than two uncached units. Ten fully reused requests cost about &lt;code&gt;1.25 + 9×0.10 = 2.15×&lt;/code&gt; instead of &lt;code&gt;10×&lt;/code&gt;. These are official illustrative calculations, not a Modelflare invoice or a promise for every model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The adapter tax is measurable
&lt;/h2&gt;

&lt;p&gt;Use a failure matrix instead of a screenshot. Keep the application prompt, model, credentials, and request rate fixed; change one variable at a time and retain the raw usage object.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Controlled change&lt;/th&gt;
&lt;th&gt;Expected provider signal&lt;/th&gt;
&lt;th&gt;What an opaque agent may hide&lt;/th&gt;
&lt;th&gt;Release interpretation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Append only the user turn&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;R&lt;/code&gt; rises after the first &lt;code&gt;W&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Rewritten history or a new route&lt;/td&gt;
&lt;td&gt;Prefix preservation works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add a timestamp to the system prompt&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;R&lt;/code&gt; falls to zero or a new &lt;code&gt;W&lt;/code&gt; appears&lt;/td&gt;
&lt;td&gt;Which bytes changed&lt;/td&gt;
&lt;td&gt;Dynamic prefix is a cache breaker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reorder one tool property&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tools_changed&lt;/code&gt; or a new write&lt;/td&gt;
&lt;td&gt;Serializer behavior&lt;/td&gt;
&lt;td&gt;Tool schema must be deterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split traffic across aliases or machines&lt;/td&gt;
&lt;td&gt;Lower and more variable &lt;code&gt;R&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Selected route and cache key&lt;/td&gt;
&lt;td&gt;Affinity policy is missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleep past the documented TTL&lt;/td&gt;
&lt;td&gt;New write after expiry&lt;/td&gt;
&lt;td&gt;Expiry time and retention mode&lt;/td&gt;
&lt;td&gt;Measure human-pause workload separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fire two identical first requests in parallel&lt;/td&gt;
&lt;td&gt;One or both may write&lt;/td&gt;
&lt;td&gt;Warm-up race and attempt order&lt;/td&gt;
&lt;td&gt;Do not infer capacity from a cold burst&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The test must retain the request ID, stream mode, first event time, exact model, selected route, cache fields, and UTC timestamp. Redact prompts, keys, and customer content. If a gateway returns only a normalized “input tokens” total, label the run &lt;strong&gt;not auditable&lt;/strong&gt;; do not turn missing dimensions into a fabricated zero or a favorable estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reproducible audit record
&lt;/h2&gt;

&lt;p&gt;The following is a small, synthetic record format. It is deliberately provider-neutral at the envelope and preserves native fields inside &lt;code&gt;usage&lt;/code&gt;. It is not a benchmark result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"request_id":"demo-001","timestamp":"2026-08-25T02:00:00Z","model":"MODEL_ID","route":"route-a","stream":true,"usage":{"input_tokens":1000000,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":1000000}}}
{"request_id":"demo-002","timestamp":"2026-08-25T02:00:03Z","model":"MODEL_ID","route":"route-a","stream":true,"usage":{"input_tokens":1000000,"input_tokens_details":{"cached_tokens":720000,"cache_write_tokens":80000}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run at least five phases: a serial warm-up, an append-only conversation, a dynamic-system mutation, a tool-order mutation, and a TTL-gap replay. For Claude, map the native read/write fields into the same report without replacing them; for OpenAI, retain both cached and write fields when returned. Compare &lt;code&gt;R/(R+W)&lt;/code&gt; and &lt;code&gt;R/(R+W+U)&lt;/code&gt; per phase, route, model, and UTC day. A single blended number is not enough to decide whether an agent is safe for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Modelflare guarantees—and what it does not
&lt;/h2&gt;

&lt;p&gt;Modelflare’s public status page currently describes an &lt;strong&gt;OpenAI Cache Hit Rate Guarantee&lt;/strong&gt;. For eligible requests that satisfy OpenAI’s caching requirements and form a valid cache, the daily rate is calculated by UTC calendar day. If the measured rate falls below the applicable tier, the shortfall is compensated and shown in &lt;strong&gt;Dashboard → Token Usage Analysis&lt;/strong&gt;. The public tiers currently shown are &lt;strong&gt;65%&lt;/strong&gt;, &lt;strong&gt;75%&lt;/strong&gt;, and &lt;strong&gt;85%&lt;/strong&gt; for the published group levels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fmodelflare-cache-guarantee.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmodelflare.dev%2Farticles%2Fdiagrams%2Fmodelflare-cache-guarantee.svg" alt="Three public guarantee tiers rise from 65 to 85 percent" width="1200" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The boundary is intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The guarantee applies to eligible, valid OpenAI cache traffic, not to a short or constantly changing prompt that never qualifies.&lt;/li&gt;
&lt;li&gt;It does not turn a changing model, tool schema, route, or TTL into a reusable prefix.&lt;/li&gt;
&lt;li&gt;It is calculated per UTC calendar day and credited through the documented account surface; it is not an assertion that every individual request will hit.&lt;/li&gt;
&lt;li&gt;The native usage evidence and eligibility calculation remain visible requirements. A compensation rule is useful precisely because the denominator is explicit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the difference between an auditable service promise and a third-party agent slogan: the former names the eligible population, observation window, threshold, and credit destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Price advantage and operating legitimacy are separate proofs
&lt;/h2&gt;

&lt;p&gt;The current public pricing page also displays a &lt;strong&gt;0.015 first-top-up offer ratio&lt;/strong&gt; for the applicable campaign/group, with the page’s stated minimum single top-up and eligibility terms. This is a price incentive; it must not be conflated with the cache guarantee. Check the live pricing page for the exact model scope, campaign status, and settlement rules before making a purchase decision.&lt;/p&gt;

&lt;p&gt;Modelflare’s public trust and legal materials identify &lt;strong&gt;Havenbyte LLC&lt;/strong&gt; as the operator and describe the operation as United States based. They list &lt;strong&gt;Stripe&lt;/strong&gt; as the payment processor for payment handling, billing, invoices, refunds, and fraud controls. These are useful custody and accountability signals, not a substitute for reading the terms or testing the API. The article makes no claim about a particular state registration, certification, or provider affiliation beyond those public materials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation for production agents
&lt;/h2&gt;

&lt;p&gt;Choose a third-party agent only when it can pass this checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It forwards the provider’s native cache controls and returns native read/write usage.&lt;/li&gt;
&lt;li&gt;It keeps the stable instruction and tool prefix byte-identical, then appends dynamic state.&lt;/li&gt;
&lt;li&gt;It exposes the selected model, route, organization/region boundary, TTL mode, and request ID.&lt;/li&gt;
&lt;li&gt;It records serial and parallel warm-up behavior instead of averaging them away.&lt;/li&gt;
&lt;li&gt;It defines eligible traffic, denominator, UTC window, threshold, and compensation in writing.&lt;/li&gt;
&lt;li&gt;It lets you export request-level evidence without exposing secrets or customer content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those answers is “no,” do not use the path for a workload whose economics depend on cache reuse. Use a direct provider path for the experiment, or choose an auditable gateway such as Modelflare and verify the exact route with the same corpus. For adjacent controls, see &lt;a href="https://modelflare.dev/articles/reliable-ai-api-routing" rel="noopener noreferrer"&gt;reliable AI API routing&lt;/a&gt;, &lt;a href="https://modelflare.dev/articles/ai-api-cost-tracking" rel="noopener noreferrer"&gt;AI API cost tracking&lt;/a&gt;, &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;, and &lt;a href="https://modelflare.dev/trust" rel="noopener noreferrer"&gt;trust&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and verification date
&lt;/h2&gt;

&lt;p&gt;The following primary sources were checked on &lt;strong&gt;2026-08-25&lt;/strong&gt;. They describe provider contracts and Modelflare’s current public policy; they do not establish a universal hit rate for an unnamed third-party agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;OpenAI Prompt Caching guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;Anthropic Prompt Caching guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics" rel="noopener noreferrer"&gt;Anthropic cache diagnostics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelflare.dev/api/status" rel="noopener noreferrer"&gt;Modelflare live status and cache guarantee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Modelflare pricing and current offer terms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelflare.dev/trust" rel="noopener noreferrer"&gt;Modelflare trust and operating entity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FAQ: Is a low third-party hit rate always fraud?
&lt;/h3&gt;

&lt;p&gt;No. It may be a valid result of short prompts, a changing prefix, route fan-out, TTL expiry, or a missing usage field. The correct finding is “not reproducible” or “not auditable” until the controlled test shows the cause.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ: Can I compare Claude and GPT with one number?
&lt;/h3&gt;

&lt;p&gt;Only after normalizing the denominator and preserving each provider’s native fields. Compare the same corpus and workload phase, not a blended dashboard total.&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ: Does compensation remove the need to design a stable prefix?
&lt;/h3&gt;

&lt;p&gt;No. Compensation limits the financial downside for eligible OpenAI cache traffic; it cannot make an ineligible request reusable or repair an agent that rewrites the prefix.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/ai-agent-cache-hit-rate?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to Evaluate an AI API Gateway: A Production Checklist</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:05:27 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/how-to-evaluate-an-ai-api-gateway-a-production-checklist-1hnj</link>
      <guid>https://dev.to/modelflare_dev/how-to-evaluate-an-ai-api-gateway-a-production-checklist-1hnj</guid>
      <description>&lt;p&gt;A reproducible gateway evaluation covering protocol conformance, failure drills, latency, usage and cost reconciliation, security, operations, and exit risk.&lt;/p&gt;

&lt;p&gt;Evaluate an AI API gateway by running your real protocol contract through it, forcing the failure modes you care about, and checking the request-level evidence it produces. A feature list or successful “hello world” request cannot prove streaming correctness, tool compatibility, fallback safety, cost accuracy, security boundaries, or a usable exit path.&lt;/p&gt;

&lt;p&gt;The most reliable process uses two kinds of criteria: non-negotiable gates that cause a candidate to fail, and scored operational qualities that distinguish candidates after every gate passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the workload contract first
&lt;/h2&gt;

&lt;p&gt;Do not begin with a vendor comparison table. Begin with one representative workload and write down its invariant contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact endpoint: Responses, Chat Completions, embeddings, images, or another API;&lt;/li&gt;
&lt;li&gt;exact model identifiers and whether aliases are permitted;&lt;/li&gt;
&lt;li&gt;streaming and non-streaming modes in use;&lt;/li&gt;
&lt;li&gt;Structured Outputs, Function Calling, hosted tools, reasoning, or other required fields;&lt;/li&gt;
&lt;li&gt;typical and high-percentile input and output lengths;&lt;/li&gt;
&lt;li&gt;concurrency, request rate, region, and user-facing deadline;&lt;/li&gt;
&lt;li&gt;required usage, cache, cost, and request-correlation fields;&lt;/li&gt;
&lt;li&gt;allowed fallback routes and whether model substitution is forbidden;&lt;/li&gt;
&lt;li&gt;data-retention, access, residency, and deletion requirements;&lt;/li&gt;
&lt;li&gt;application operations that can create side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One gateway can pass for a text-only internal assistant and fail for a streaming coding agent. “OpenAI-compatible” is not a sufficient workload definition because compatibility can differ by endpoint, event type, tool, schema keyword, and provider route.&lt;/p&gt;

&lt;p&gt;If the team is still deciding whether it needs a proxy or a model-aware control plane, start with &lt;a href="https://modelflare.dev/articles/llm-proxy-vs-ai-gateway" rel="noopener noreferrer"&gt;LLM Proxy vs AI Gateway&lt;/a&gt;. This checklist assumes the gateway category is already justified and tests whether a specific implementation can own the required responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply fast disqualifiers before a long trial
&lt;/h2&gt;

&lt;p&gt;The first review should remove candidates that cannot satisfy a mandatory boundary. Ask for demonstrable behavior, not a roadmap statement.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Immediate failure condition&lt;/th&gt;
&lt;th&gt;Evidence to request&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;A required request field, output item, or stream event is dropped or rewritten incorrectly&lt;/td&gt;
&lt;td&gt;Captured redacted request/response pair and parser result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model identity&lt;/td&gt;
&lt;td&gt;The gateway silently changes the requested model&lt;/td&gt;
&lt;td&gt;Attempt record with requested and actual model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;It buffers the full response, loses cancellation, or corrupts Tool Argument fragments&lt;/td&gt;
&lt;td&gt;Timestamped event sequence and cancellation trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Browser or workload clients receive provider credentials&lt;/td&gt;
&lt;td&gt;Credential-flow diagram and a live key-rotation test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant isolation&lt;/td&gt;
&lt;td&gt;One project can use or inspect another project’s keys, usage, or logs&lt;/td&gt;
&lt;td&gt;Authorization tests using real isolated accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost evidence&lt;/td&gt;
&lt;td&gt;A final charge cannot be connected to model, route, price basis, and usage&lt;/td&gt;
&lt;td&gt;One reconciled request ledger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure safety&lt;/td&gt;
&lt;td&gt;A partial stream is transparently replayed or a cancellation starts another attempt&lt;/td&gt;
&lt;td&gt;Forced partial-stream and cancel traces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export and exit&lt;/td&gt;
&lt;td&gt;Configuration and request contract cannot be recovered without rewriting the application&lt;/td&gt;
&lt;td&gt;Export sample and provider-native rollback drill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A candidate that fails a mandatory gate should not receive a high aggregate score to compensate. Security isolation cannot be offset by a good dashboard, and an incorrect Tool Contract cannot be offset by a lower price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small protocol conformance corpus
&lt;/h2&gt;

&lt;p&gt;Use deterministic, non-sensitive inputs and keep the expected wire behavior under version control. The corpus should call the real gateway endpoint; it should not mock the provider or reimplement the gateway’s conversion logic as the test oracle.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Required observation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic non-streaming text&lt;/td&gt;
&lt;td&gt;Pinned model and fixed prompt&lt;/td&gt;
&lt;td&gt;Correct status, model identity, text location, usage, request ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming text&lt;/td&gt;
&lt;td&gt;Same prompt with streaming enabled&lt;/td&gt;
&lt;td&gt;Ordered events, first effective output, final event, cancellation behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured output&lt;/td&gt;
&lt;td&gt;Strict schema with required fields and &lt;code&gt;additionalProperties: false&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Valid output or an explicit unsupported error; no silent downgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function Calling&lt;/td&gt;
&lt;td&gt;One read-only function and one returned result&lt;/td&gt;
&lt;td&gt;Function name, JSON arguments, Call ID correlation, final answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No-tool path&lt;/td&gt;
&lt;td&gt;Same tools declared but no tool required&lt;/td&gt;
&lt;td&gt;Normal text without a fabricated Tool Call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid field&lt;/td&gt;
&lt;td&gt;Deliberately unsupported or malformed request&lt;/td&gt;
&lt;td&gt;Stable client error; no provider fallback that hides the defect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long input boundary&lt;/td&gt;
&lt;td&gt;Input just below and above the approved limit&lt;/td&gt;
&lt;td&gt;Documented acceptance or explicit rejection, not truncation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage detail&lt;/td&gt;
&lt;td&gt;Request that exercises cached or reasoning usage when supported&lt;/td&gt;
&lt;td&gt;Fields survive the route and reconcile with the billing record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellation&lt;/td&gt;
&lt;td&gt;Client cancels after connection and after first output&lt;/td&gt;
&lt;td&gt;Upstream work stops and no new fallback attempt starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial stream&lt;/td&gt;
&lt;td&gt;Connection fails after effective output&lt;/td&gt;
&lt;td&gt;One explicit partial failure; no invisible second answer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run every required case against every route that may serve the workload. A primary route passing does not qualify its fallback. The &lt;a href="https://modelflare.dev/articles/structured-outputs-openai-compatible-api" rel="noopener noreferrer"&gt;Structured Outputs guide&lt;/a&gt; and &lt;a href="https://modelflare.dev/articles/function-calling-responses-vs-chat-completions" rel="noopener noreferrer"&gt;Function Calling comparison&lt;/a&gt; provide field-level test cases for those two capabilities.&lt;/p&gt;

&lt;p&gt;Record the gateway version, route configuration version, model ID, provider, region, timestamp, and sanitized result hash. Volatile model and provider behavior should be rechecked before rollout and after a material route change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test routing and failure behavior, not only success
&lt;/h2&gt;

&lt;p&gt;Reliability claims matter only when the failure policy is visible. Force each condition before production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;primary route unavailable before headers;&lt;/li&gt;
&lt;li&gt;provider rate limit with and without &lt;code&gt;Retry-After&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;upstream authentication or account failure;&lt;/li&gt;
&lt;li&gt;slow headers and slow first effective output;&lt;/li&gt;
&lt;li&gt;malformed provider response;&lt;/li&gt;
&lt;li&gt;client cancellation while the upstream is pending;&lt;/li&gt;
&lt;li&gt;connection loss after visible output begins;&lt;/li&gt;
&lt;li&gt;all eligible routes exhausted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each case, capture the attempt order, selected route, status, timing, whether output had started, terminal reason, and any usage or cost. Confirm that the gateway preserves the requested model and protocol unless a separate model-substitution policy is explicitly enabled.&lt;/p&gt;

&lt;p&gt;Measure attempt amplification across the SDK, application, gateway, and provider. One layer should own immediate same-contract fallback, while the application owns whether the entire user action can be repeated. The &lt;a href="https://modelflare.dev/articles/ai-api-fallback-matrix" rel="noopener noreferrer"&gt;AI API Fallback Strategy&lt;/a&gt; provides a phase-aware failure matrix and retry-budget model.&lt;/p&gt;

&lt;p&gt;Latency needs the same precision. Compare time to upstream headers, first SSE event, first effective output, first visible text, completion, and visible output speed at realistic concurrency. Do not accept one unexplained “latency” average. See &lt;a href="https://modelflare.dev/articles/ai-api-latency-metrics" rel="noopener noreferrer"&gt;AI API Latency Metrics&lt;/a&gt; for definitions and comparison controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile usage and cost from one request
&lt;/h2&gt;

&lt;p&gt;Choose several completed requests and follow each one through the full accounting chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application request ID
  → gateway attempt sequence
  → selected model and route
  → provider or normalized usage
  → applicable price basis
  → final recorded charge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evaluation should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are input, output, cached, reasoning, and tool-related units represented when applicable?&lt;/li&gt;
&lt;li&gt;Which values come from the provider and which are estimated?&lt;/li&gt;
&lt;li&gt;When is the model price selected, and is it frozen for the request?&lt;/li&gt;
&lt;li&gt;Does a group, service tier, discount, or surcharge change the user charge?&lt;/li&gt;
&lt;li&gt;Which failed attempts can create provider cost, and how are they recorded?&lt;/li&gt;
&lt;li&gt;Can a final successful fallback hide earlier billable attempts?&lt;/li&gt;
&lt;li&gt;Are currency conversion and rounding rules explicit?&lt;/li&gt;
&lt;li&gt;Can finance reproduce a daily total from immutable request records?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test a normal completion, a same-contract fallback, a cancelled request, and an upstream error. A dashboard total is not enough; the gateway must produce a defensible per-request record. &lt;a href="https://modelflare.dev/articles/ai-api-cost-tracking" rel="noopener noreferrer"&gt;AI API Cost Tracking&lt;/a&gt; separates provider usage, platform pricing, customer charge, and supplier cost.&lt;/p&gt;

&lt;p&gt;Do not compare vendor savings unless the same model, workload, cache behavior, output length, failure rate, and provider price basis are held constant. A lower apparent cost can be caused by missing usage or silent model substitution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the security and data boundary
&lt;/h2&gt;

&lt;p&gt;Draw the actual data flow from the client to the gateway and each provider. For every hop, identify who can read credentials, request content, response content, metadata, and administrative configuration.&lt;/p&gt;

&lt;p&gt;At minimum, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider credentials are stored server-side, encrypted at rest, and never returned to ordinary clients;&lt;/li&gt;
&lt;li&gt;application keys can be scoped by project or workload and revoked independently;&lt;/li&gt;
&lt;li&gt;authorization is enforced server-side on every management and log endpoint;&lt;/li&gt;
&lt;li&gt;logs avoid full API keys and have explicit prompt/response retention controls;&lt;/li&gt;
&lt;li&gt;support access is attributable and bounded;&lt;/li&gt;
&lt;li&gt;configuration changes have actor, time, before/after state, and rollback evidence;&lt;/li&gt;
&lt;li&gt;exported traces redact secrets and personal or proprietary content;&lt;/li&gt;
&lt;li&gt;deletion and retention behavior can be demonstrated, not only described;&lt;/li&gt;
&lt;li&gt;region and subprocessor claims match the route actually used;&lt;/li&gt;
&lt;li&gt;abuse limits run before expensive upstream work when possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask what happens during key rotation, an operator departure, a compromised application key, and a provider-key leak. Run a rotation and revocation exercise with real isolated test credentials. Do not copy a production secret into an evaluation environment.&lt;/p&gt;

&lt;p&gt;The gateway cannot make unsafe application tools safe. Tool authorization, transactionality, approval, and idempotency remain application responsibilities even when model access is centralized. Use &lt;a href="https://modelflare.dev/articles/api-key-security-cost-controls" rel="noopener noreferrer"&gt;AI API Key Security and Cost Controls&lt;/a&gt; to separate credentials and workload limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate the operational control plane
&lt;/h2&gt;

&lt;p&gt;The data plane may work while the control plane creates operational risk. Review how operators make and recover from changes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Questions to answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Versioning&lt;/td&gt;
&lt;td&gt;Is each route, price, policy, and key change versioned or attributable?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Can an invalid route or incompatible model be rejected before activation?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollout&lt;/td&gt;
&lt;td&gt;Can a change target a small workload or percentage first?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;Can operators restore the last known-good configuration quickly?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;What happens to existing and new requests if the control plane is unavailable?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health&lt;/td&gt;
&lt;td&gt;Is channel health based on current evidence, and can automatic disablement be inspected?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incidents&lt;/td&gt;
&lt;td&gt;Can one request be reconstructed without searching several unrelated systems?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limits&lt;/td&gt;
&lt;td&gt;Are rate and quota decisions atomic enough to remain correct under concurrency?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change ownership&lt;/td&gt;
&lt;td&gt;Are emergency edits separated from normal product configuration?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run one configuration rollback and one unhealthy-route removal. Measure the operator steps and verify the resulting data-plane behavior. A screenshot of a rollback button is not the same as a completed rollback drill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the exit path before signing
&lt;/h2&gt;

&lt;p&gt;Gateway adoption can create dependencies on model aliases, custom headers, proprietary route names, log APIs, normalized error shapes, or hosted Prompt and Tool configuration. List each dependency and decide whether it is an intentional benefit or accidental lock-in.&lt;/p&gt;

&lt;p&gt;A practical exit drill should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;export route, key-policy, price, and audit configuration in a documented format;&lt;/li&gt;
&lt;li&gt;switch one workload to a provider-native test endpoint;&lt;/li&gt;
&lt;li&gt;replace gateway-only headers or aliases with explicit application configuration;&lt;/li&gt;
&lt;li&gt;preserve request correlation and usage reconciliation during the switch;&lt;/li&gt;
&lt;li&gt;document functionality that cannot move without redesign;&lt;/li&gt;
&lt;li&gt;estimate exit engineering from the observed work, not a sales claim.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An exit path does not require the gateway to be interchangeable with every provider. It requires the team to know what it owns, what the gateway owns, and how to recover the underlying protocol contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Score only after every mandatory gate passes
&lt;/h2&gt;

&lt;p&gt;Use a worksheet with &lt;code&gt;pass/fail&lt;/code&gt; for hard boundaries and a small evidence score for operational quality:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Unsupported or contradicted by the test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Claimed or manually demonstrated once, with weak evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Repeatably demonstrated with request-level evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Repeatably demonstrated, monitored, and recoverable through a tested control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Suggested scored areas are protocol coverage, route reliability, attempt evidence, latency diagnostics, usage accuracy, cost reconciliation, key isolation, auditability, configuration rollback, supportability, and exit effort. Weight them according to the workload, but keep the raw evidence beside every score.&lt;/p&gt;

&lt;p&gt;Avoid a false-precision total such as &lt;code&gt;87.4/100&lt;/code&gt; when several rows are subjective. Record the decision as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mandatory gates and outcomes;&lt;/li&gt;
&lt;li&gt;score by area with evidence links;&lt;/li&gt;
&lt;li&gt;accepted gaps and owner;&lt;/li&gt;
&lt;li&gt;remediation deadline;&lt;/li&gt;
&lt;li&gt;cost and contract assumptions;&lt;/li&gt;
&lt;li&gt;selected candidate and rejected alternatives;&lt;/li&gt;
&lt;li&gt;review date after the first production month.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compare build versus buy by ownership
&lt;/h2&gt;

&lt;p&gt;The right question is not whether an internal gateway has a license fee. It is which responsibilities the team can own continuously.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;If built internally&lt;/th&gt;
&lt;th&gt;If purchased or managed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol updates&lt;/td&gt;
&lt;td&gt;Track provider schemas and regressions&lt;/td&gt;
&lt;td&gt;Verify vendor updates and route compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing and retry&lt;/td&gt;
&lt;td&gt;Design state machine and failure evidence&lt;/td&gt;
&lt;td&gt;Configure policy and audit actual attempts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage and billing&lt;/td&gt;
&lt;td&gt;Normalize usage and maintain pricing logic&lt;/td&gt;
&lt;td&gt;Reconcile vendor records with internal finance truth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Store secrets, enforce tenancy, audit access&lt;/td&gt;
&lt;td&gt;Validate vendor boundary and configure least privilege&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;Operate data plane, control plane, and on-call&lt;/td&gt;
&lt;td&gt;Monitor vendor plus your integration and retain an exit path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product support&lt;/td&gt;
&lt;td&gt;Diagnose every application/provider interaction&lt;/td&gt;
&lt;td&gt;Triage gateway versus provider versus application failures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not insert generic salary or “engineering time saved” figures. Estimate from your own on-call load, protocol-change history, incident frequency, finance requirements, and compliance work. A managed product still requires an accountable internal owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply the checklist to Modelflare accurately
&lt;/h2&gt;

&lt;p&gt;For Modelflare, the current evaluation boundary should be explicit. It provides workload API Keys, requested-model routing through eligible groups and channels, ordered group fallback for regular keys, strategy-based group selection for Smart API Keys, pre-upstream group RPM admission, and request-level usage, cost, status, and timing records.&lt;/p&gt;

&lt;p&gt;GPT, Codex, and OpenAI traffic is the fully adapted compatibility target. Other OpenAI-compatible model families should be evaluated as raw Chat Completions pass-through unless a capability has been separately verified. A shared base URL is not proof that Responses, hosted tools, Structured Outputs, or Function Calling behave identically on every route.&lt;/p&gt;

&lt;p&gt;Modelflare’s fallback should search for an eligible path for the requested model rather than silently selecting another model. Channel failover stops after downstream output starts. Those claims can be evaluated with the protocol corpus and failure drills above instead of accepted as marketing statements.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Models &amp;amp; Pricing&lt;/a&gt; to identify the current model and group surface and &lt;a href="https://modelflare.dev/docs" rel="noopener noreferrer"&gt;Modelflare Docs&lt;/a&gt; to configure an isolated test key. Keep the evaluation requests non-sensitive, pin the exact model, and retain the Request IDs needed to inspect each attempt.&lt;/p&gt;

&lt;p&gt;The final decision should be reproducible: another engineer should be able to run the same corpus, inspect the same categories of evidence, and understand why the candidate passed. That process is slower than reading a comparison page, but it is much faster than discovering an incompatible Tool Contract, untraceable bill, or unsafe fallback after the gateway owns production traffic.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/evaluate-ai-api-gateway?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>architecture</category>
      <category>devops</category>
    </item>
    <item>
      <title>Claude Opus 5.5: Specs, Price, and Place in the Field</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:09:59 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/claude-opus-55-specs-price-and-place-in-the-field-3jal</link>
      <guid>https://dev.to/modelflare_dev/claude-opus-55-specs-price-and-place-in-the-field-3jal</guid>
      <description>&lt;p&gt;A source-backed account of Claude Opus 5.5: published specs, official token prices, how to read the benchmark table against Fable 5.1, Opus 5, and GPT-6 Astra, and the Modelflare groups and user prices returned on 30 September 2026.&lt;/p&gt;

&lt;p&gt;Claude Opus 5.5 was released on 22 September 2026. The public contract is on the model page and in the what's-new note: API id &lt;code&gt;claude-opus-5-5&lt;/code&gt;, a 1M-token context window, 128K max output, adaptive thinking that stays on, default effort &lt;code&gt;medium&lt;/code&gt;, and a list price of $4 per million input tokens and $20 per million output tokens. Anthropic's pages do not publish a parameter count or a training-compute figure. This article stays with the published spec, the published price card, and the published benchmark table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checked conclusion:&lt;/strong&gt; on Anthropic's list price, Opus 5.5 is the current Opus tier. Per token it is cheaper than Opus 5, and much cheaper than Fable 5.1. On the coding and knowledge-work rows Anthropic printed, its scores lead Fable 5.1, Opus 5, and, on most of those rows, GPT-6 Astra. Two printed rows go the other way: Astra leads Terminal-Bench-Science 0.1 and Zapier's AutomationBench. Modelflare's pricing API, read on 30 September 2026, includes &lt;code&gt;claude-opus-5-5&lt;/code&gt; at the official $4 / $20 unit prices. That row enables &lt;code&gt;claude-award&lt;/code&gt; 0.25, &lt;code&gt;claude-stable&lt;/code&gt; 0.315, and &lt;code&gt;claude-premium&lt;/code&gt; 0.5. A key for this tier sends &lt;code&gt;claude-opus-5-5&lt;/code&gt;. Live rates are on the &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Official behavior below is cited from the &lt;a href="https://platform.claude.com/docs/en/models/opus-5-5/overview" rel="noopener noreferrer"&gt;Opus 5.5 model page&lt;/a&gt;, &lt;a href="https://platform.claude.com/docs/en/models/opus-5-5/whats-new-opus-5-5" rel="noopener noreferrer"&gt;what's new&lt;/a&gt;, and the &lt;a href="https://www.anthropic.com/claude-opus-5-5" rel="noopener noreferrer"&gt;launch page&lt;/a&gt;. The earlier &lt;a href="https://modelflare.dev/articles/claude-fable-5-1-review-pricing" rel="noopener noreferrer"&gt;Fable 5.1 review&lt;/a&gt; is the place for that model's migration notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Published specifications
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Claude Opus 5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model id, Claude API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5-5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Bedrock id&lt;/td&gt;
&lt;td&gt;&lt;code&gt;anthropic.claude-opus-5-5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Cloud and Microsoft Foundry id&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5-5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release date&lt;/td&gt;
&lt;td&gt;22 September 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;1M tokens, both the default and the maximum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max output&lt;/td&gt;
&lt;td&gt;128K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thinking&lt;/td&gt;
&lt;td&gt;Adaptive, always on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default effort&lt;/td&gt;
&lt;td&gt;&lt;code&gt;medium&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency class on the model page&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliable knowledge cutoff&lt;/td&gt;
&lt;td&gt;June 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameter count&lt;/td&gt;
&lt;td&gt;Not on the public model page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Reliable knowledge cutoff" is Anthropic's name for the date through which the model's stored knowledge is most extensive. It is separate from the context window. A file you attach is still in the request.&lt;/p&gt;

&lt;p&gt;Fast mode is a speed switch on the same id, documented as a research preview on the Claude API only. The request sets &lt;code&gt;speed: "fast"&lt;/code&gt; with the &lt;code&gt;fast-mode-2026-02-01&lt;/code&gt; beta header. Anthropic prices that mode at $8 / $40 per million tokens and describes output as up to 2.5× faster. The what's-new page says this fast mode is absent on Amazon Bedrock, Claude Platform on AWS, Google Cloud, and Microsoft Foundry.&lt;/p&gt;

&lt;p&gt;The same page lists the effort ladder as the control for thinking depth: &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt;, and &lt;code&gt;max&lt;/code&gt;. A manual thinking budget is rejected. The minimum cacheable prompt is 512 tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Official list prices
&lt;/h3&gt;

&lt;p&gt;Figures are US dollars per million tokens. Opus 5.5's cache, batch, and fast-mode rows come from the what's-new pricing section. Opus 5's cache-write cell is the single "cache writes" number on the launch comparison table, which does not split the 5-minute and 1-hour tiers. Fable 5.1's cache read is the official figure already used in this blog's Fable 5.1 review. Sonnet 5 and Haiku 4.5 input and output prices are the comparison row on the Opus 5.5 model page. An em dash means this article did not take that cell from a primary price row.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Price per 1M tokens&lt;/th&gt;
&lt;th&gt;Opus 5.5&lt;/th&gt;
&lt;th&gt;Opus 5&lt;/th&gt;
&lt;th&gt;Fable 5.1&lt;/th&gt;
&lt;th&gt;Sonnet 5&lt;/th&gt;
&lt;th&gt;Haiku 4.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$4&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;$50&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache write&lt;/td&gt;
&lt;td&gt;$5 for 5 minutes; $8 for 1 hour&lt;/td&gt;
&lt;td&gt;$6.25&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch input / output&lt;/td&gt;
&lt;td&gt;$2 / $10&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast mode input / output&lt;/td&gt;
&lt;td&gt;$8 / $40&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The list-price move from Opus 5 is 20% on fresh input and output, from $5 / $25 to $4 / $20. Cache reads, which dominate long agent loops, move from $0.50 to $0.20. That is 60% off the Opus 5 cache-read price, and it also sits under Fable 5.1's $0.25 cache read. Anthropic's separate workload claim is that a typical run at default settings costs about 40% less than Opus 5, because the model also uses fewer tokens. That 40% describes their harness. A real bill still follows effort, cache hit rate, and tool rounds.&lt;/p&gt;

&lt;p&gt;Batch processing is half the new list price. Fast mode is double the new list price, on the Claude API only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelflare catalog on 30 September 2026
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GET https://modelflare.dev/api/pricing&lt;/code&gt; on 30 September 2026 includes &lt;code&gt;claude-opus-5-5&lt;/code&gt;. The same response also includes &lt;code&gt;claude-opus-5&lt;/code&gt;, &lt;code&gt;claude-fable-5-1&lt;/code&gt;, &lt;code&gt;claude-sonnet-5&lt;/code&gt;, &lt;code&gt;claude-sonnet-5-5&lt;/code&gt;, &lt;code&gt;claude-haiku-4-5&lt;/code&gt;, &lt;code&gt;gpt-6-astra&lt;/code&gt;, and &lt;code&gt;gpt-5.6-sol&lt;/code&gt;. It does not include a Haiku 5.5 id.&lt;/p&gt;

&lt;p&gt;Unit prices below are that response's token prices before a group ratio. For the Claude rows they match the official list. Astra and Sol are tiered: the second pair applies when input context is above 272,000 tokens, and the long-context output price is the tier's own output price. The example column multiplies Claude rows by &lt;code&gt;claude-stable&lt;/code&gt; 0.315 and OpenAI rows by &lt;code&gt;openai-stable&lt;/code&gt; 0.08. Claude ratios on this read are &lt;code&gt;claude-premium&lt;/code&gt; 0.5, &lt;code&gt;claude-stable&lt;/code&gt; 0.315, and &lt;code&gt;claude-award&lt;/code&gt; 0.25. OpenAI ratios are &lt;code&gt;openai-premium&lt;/code&gt; 0.13, &lt;code&gt;openai-stable&lt;/code&gt; 0.08, and &lt;code&gt;openai-award&lt;/code&gt; 0.03. Opus 5.5 enables only the three Claude groups above. &lt;code&gt;claude-tmp-cheap&lt;/code&gt; is a public Claude group, and this model row does not include it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Catalog id&lt;/th&gt;
&lt;th&gt;Unit input / output&lt;/th&gt;
&lt;th&gt;User price on the stable group&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opus 5.5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$4 / $20&lt;/td&gt;
&lt;td&gt;$1.26 / $6.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opus 5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$5 / $25&lt;/td&gt;
&lt;td&gt;$1.575 / $7.875&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fable 5.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-fable-5-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$10 / $50&lt;/td&gt;
&lt;td&gt;$3.15 / $15.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-sonnet-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$2 / $10&lt;/td&gt;
&lt;td&gt;$0.63 / $3.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-6 Astra&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-6-astra&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$10 / $50, then $20 / $75&lt;/td&gt;
&lt;td&gt;$0.80 / $4.00, then $1.60 / $6.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6 Sol&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-5.6-sol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$5 / $30, then $10 / $45&lt;/td&gt;
&lt;td&gt;$0.40 / $2.40, then $0.80 / $3.60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Opus 5.5 on &lt;code&gt;claude-stable&lt;/code&gt; is $4 × 0.315 and $20 × 0.315. Use the official table to compare labs. Use the catalog table to read a Modelflare bill you can send today.&lt;/p&gt;

&lt;p&gt;Haiku 4.5 is in the same catalog at a $1 / $5 unit price. On &lt;code&gt;claude-stable&lt;/code&gt; that is $0.315 / $1.575. The model-page comparison still gives Haiku a 200K context window and 64K max output. Haiku 5.5 is not in the 30 September response. Haiku 4.5 remains the low end of the Claude line that is actually listed.&lt;/p&gt;

&lt;p&gt;Cache ratios on the catalog rows are a fraction of the input unit price: 0.05 for Opus 5.5, so the catalog cache read is $0.20 and the catalog cache write is $5. That write matches the official 5-minute tier. The official 1-hour write is $8 and is not a separate catalog price. Opus 5 and Sonnet 5 use 0.1, and Fable 5.1 uses 0.025. Settle cache, long context, and priority tiers from the pricing page and from the finished usage record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed versus Opus 5 and Fable 5.1
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;Opus 5.5&lt;/th&gt;
&lt;th&gt;Opus 5&lt;/th&gt;
&lt;th&gt;Fable 5.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API id&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-opus-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-fable-5-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context / max output&lt;/td&gt;
&lt;td&gt;1M / 128K&lt;/td&gt;
&lt;td&gt;1M / 128K&lt;/td&gt;
&lt;td&gt;1M / 128K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thinking&lt;/td&gt;
&lt;td&gt;Always on. &lt;code&gt;disabled&lt;/code&gt; and a manual budget return 400&lt;/td&gt;
&lt;td&gt;On by default. &lt;code&gt;disabled&lt;/code&gt; is accepted only at effort &lt;code&gt;high&lt;/code&gt; or below&lt;/td&gt;
&lt;td&gt;Always on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default effort when the field is omitted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliable knowledge cutoff&lt;/td&gt;
&lt;td&gt;June 2026&lt;/td&gt;
&lt;td&gt;May 2026&lt;/td&gt;
&lt;td&gt;June 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List input / output&lt;/td&gt;
&lt;td&gt;$4 / $20&lt;/td&gt;
&lt;td&gt;$5 / $25&lt;/td&gt;
&lt;td&gt;$10 / $50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forced tool choice&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;any&lt;/code&gt; and &lt;code&gt;tool&lt;/code&gt; return 400&lt;/td&gt;
&lt;td&gt;Listed as a breaking change away from Opus 5&lt;/td&gt;
&lt;td&gt;Same 400 as Opus 5.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cybersecurity safeguard&lt;/td&gt;
&lt;td&gt;Hands the task to Opus 4.8&lt;/td&gt;
&lt;td&gt;The launch note does not put this fallback on Opus 5&lt;/td&gt;
&lt;td&gt;Fable-class safeguards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Biology and frontier-LLM safeguard&lt;/td&gt;
&lt;td&gt;Hands the task to Opus 5&lt;/td&gt;
&lt;td&gt;The launch note pairs this fallback with Opus 5.5&lt;/td&gt;
&lt;td&gt;Same safeguard class&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Opus 5.5 reads thinking blocks from Opus 5 and from earlier Opus, Sonnet, and Haiku models. It does not read thinking blocks from Fable or Mythos. On the Claude API, Fable 5.1 and Mythos 5.1 read Opus 5.5 thinking blocks, and the what's-new page says no other current model does. A block the target model cannot read is dropped before the model sees it, and a dropped block is not billed.&lt;/p&gt;

&lt;p&gt;Accounts created on or after 31 August 2026 get a stricter check: if the system prompt, the tool list, or an earlier message changed after an Opus 5.5 thinking block was produced, replaying that block returns 400. The migration guide's path is an append-only history, or a mid-conversation system message when instructions have to change. The launch page also says Opus 5.5 ships with the preserved-thinking control introduced on Fable 5.1, with EU AI Act watermarking, and with zero data retention, in line with earlier Opus models.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read the intelligence scores
&lt;/h2&gt;

&lt;p&gt;The table is the one printed on the launch page. Anthropic says that, unless a footnote says otherwise, Opus 5.5 ran with adaptive thinking at max effort. Terminal-Bench 4.0 is the footnote: Opus 5.5 at &lt;code&gt;xhigh&lt;/code&gt;, GPT-6 Astra at &lt;code&gt;high&lt;/code&gt;, each as that model's highest reported score. Astra and Sol numbers are quoted as reported by OpenAI. Production safeguards were on. When they fired, cybersecurity tasks were completed by Opus 4.8, and biology plus frontier LLM-development tasks were completed by Opus 5. Anthropic says that pulls Opus 5.5's scores down.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Opus 5.5&lt;/th&gt;
&lt;th&gt;Fable 5.1&lt;/th&gt;
&lt;th&gt;Opus 5&lt;/th&gt;
&lt;th&gt;GPT-6 Astra&lt;/th&gt;
&lt;th&gt;GPT-5.6 Sol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 4.0&lt;/td&gt;
&lt;td&gt;66.4%&lt;/td&gt;
&lt;td&gt;55.8%&lt;/td&gt;
&lt;td&gt;52.3%&lt;/td&gt;
&lt;td&gt;57.9%&lt;/td&gt;
&lt;td&gt;37.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FrontierCode v1.1 (Main)&lt;/td&gt;
&lt;td&gt;54.4%&lt;/td&gt;
&lt;td&gt;50.3%&lt;/td&gt;
&lt;td&gt;48.0%&lt;/td&gt;
&lt;td&gt;53.3%&lt;/td&gt;
&lt;td&gt;47.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CursorBench 4.0&lt;/td&gt;
&lt;td&gt;57.8%&lt;/td&gt;
&lt;td&gt;51.8%&lt;/td&gt;
&lt;td&gt;46.6%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;41.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPval-AA v2.1&lt;/td&gt;
&lt;td&gt;1846&lt;/td&gt;
&lt;td&gt;1735&lt;/td&gt;
&lt;td&gt;1708&lt;/td&gt;
&lt;td&gt;1542&lt;/td&gt;
&lt;td&gt;1588&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AutomationBench&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;31.4%&lt;/td&gt;
&lt;td&gt;26.9%&lt;/td&gt;
&lt;td&gt;41.4%&lt;/td&gt;
&lt;td&gt;28.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humanity's Last Exam, with tools&lt;/td&gt;
&lt;td&gt;67.7%&lt;/td&gt;
&lt;td&gt;65.6%&lt;/td&gt;
&lt;td&gt;63.6%&lt;/td&gt;
&lt;td&gt;57.2%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench-Science 0.1&lt;/td&gt;
&lt;td&gt;58.7%&lt;/td&gt;
&lt;td&gt;52.6%&lt;/td&gt;
&lt;td&gt;29.0%&lt;/td&gt;
&lt;td&gt;64.6%&lt;/td&gt;
&lt;td&gt;22.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSWorld 2.0, partial&lt;/td&gt;
&lt;td&gt;81.8%&lt;/td&gt;
&lt;td&gt;80.7%&lt;/td&gt;
&lt;td&gt;74.0%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chartography, with tools&lt;/td&gt;
&lt;td&gt;89.0%&lt;/td&gt;
&lt;td&gt;88.4%&lt;/td&gt;
&lt;td&gt;83.4%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Put the gaps next to the error bars they published. Terminal-Bench 4.0 is ±2.6 points for Opus 5.5 and ±1.6 to ±2 points for the other Claude models. The gap from 66.4% to Astra's 57.9%, and to Fable's 55.8%, is wider than those bars. Terminal-Bench-Science is ±3.5 to ±5 points per model. Astra's 64.6% against Opus 5.5's 58.7% sits near the width of those bars, with Astra ahead in the printed point estimate.&lt;/p&gt;

&lt;p&gt;AutomationBench was run by Zapier. Safeguard interventions counted as failures because the run had no fallback model. Anthropic says the practical score would be higher. The printed 40.0% against Astra's 41.4% is a close published result, with Astra ahead on the point estimate. GDPval-AA v2.1 is an Elo on real professional tasks across 44 occupations: 1846 for Opus 5.5, 1735 for Fable 5.1, 1708 for Opus 5, 1588 for Sol, 1542 for Astra.&lt;/p&gt;

&lt;p&gt;The same launch page prints a second pair of scores on the cost charts, taken at default effort &lt;code&gt;medium&lt;/code&gt;: FrontierCode 54.6% and CursorBench 52.5%. The comparison table prints 54.4% and 57.8%. Keep them as two Anthropic figures at two effort settings. The chart caption also says that, at medium effort, FrontierCode beats Astra's top score of 53.3% at about a fifth of the cost per task, and that Terminal-Bench matches Astra at about 40% of the cost. Those are cost-per-task claims about Anthropic's harness.&lt;/p&gt;

&lt;p&gt;The sentence under the table is the one to keep: in Anthropic's own use, the gap between Opus 5.5 and Fable 5.1 is narrower than the scores, and benchmark margins at this level are a weaker guide to a real task. Customer quotes on the launch page are selected testimonials. They can suggest what to replay. They do not belong in the score table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it sits among current models
&lt;/h2&gt;

&lt;p&gt;Three checkable shifts landed on the same day.&lt;/p&gt;

&lt;p&gt;The work Anthropic describes as Fable 5.1 level for most tasks now has an Opus price card. List price is $4 / $20 against Fable 5.1 at $10 / $50. Cache reads are $0.20 against Fable's $0.25 and Opus 5's $0.50. Where a team's own replay agrees, Fable 5.1 stays the model for the tasks that still fail, and Opus 5.5 is the model that should run the loop. Where the replay disagrees, the table already shows the published exceptions: science-agent tasks and Zapier's business workflows, where Astra leads the printed numbers.&lt;/p&gt;

&lt;p&gt;The default moved with the price. Claude Code's model configuration, updated with this release, sets Opus 5.5 as the default for Pro, Max, Team, Enterprise, and the Anthropic API, and also for Claude Platform on AWS, Amazon Bedrock, and Google Cloud's Agent Platform. A default inside the agent product moves more traffic than a new row in a picker.&lt;/p&gt;

&lt;p&gt;The safeguard boundary moved down onto Opus. Earlier Opus models were where traffic went when a Fable classifier would not answer. Opus 5.5 launches with a similar set: cybersecurity tasks are completed by Opus 4.8, and biology plus frontier LLM-development tasks are completed by Opus 5. A log line that records the requested id &lt;code&gt;claude-opus-5-5&lt;/code&gt; can sit on a completion produced by a different model. Refusals return HTTP 200 with &lt;code&gt;stop_reason: "refusal"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sonnet 5.5 is already in the 30 September catalog at the same $2 / $10 unit price as Sonnet 5. Haiku 5.5 is not in that response. The cheaper listed Claude cards are Sonnet 5.5, Sonnet 5 at $2 / $10, and Haiku 4.5 at $1 / $5. Haiku 4.5 remains on a 200K window. Sonnet 5.5's groups are in the &lt;a href="https://modelflare.dev/articles/claude-sonnet-5-5-review-pricing" rel="noopener noreferrer"&gt;Sonnet 5.5 review&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a position you can audit from documents: list price, the Claude Code default, which model answers when a safeguard fires, and which rows of the vendor table Astra still leads. It is not a market-share claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration constraints
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requests that return 400
&lt;/h3&gt;

&lt;p&gt;A client that only replaces the model string still hits four breaks documented for code that was running on Opus 5. The first three also apply to Fable 5.1. The shape below is the one the migration note tells you to send. The comments mark the variants that return &lt;code&gt;invalid_request_error&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model: claude-opus-5-5
thinking: {"type": "adaptive"}
tool_choice: {"type": "auto"}
output_config.effort: medium
# 400: thinking type disabled, or enabled with budget_tokens
# 400: tool_choice any, or tool_choice tool
# 400 on the Claude API and Google Cloud: tool type computer_20251124
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depth replaces the old on/off thinking switch. Omit &lt;code&gt;thinking&lt;/code&gt;, or send &lt;code&gt;adaptive&lt;/code&gt;. Set effort explicitly. &lt;code&gt;tool_choice&lt;/code&gt; &lt;code&gt;auto&lt;/code&gt; and &lt;code&gt;none&lt;/code&gt; remain valid, including on the token-counting endpoint. For schema-valid JSON, the migration note points to strict tool use or structured outputs, plus a prompt line that says when the tool applies.&lt;/p&gt;

&lt;p&gt;On the Claude API and Google Cloud, replace &lt;code&gt;computer_20251124&lt;/code&gt; with the &lt;code&gt;computer_toolset_20260801&lt;/code&gt; toolset and drop the old computer-use beta header. Amazon Bedrock still accepts &lt;code&gt;computer_20251124&lt;/code&gt; on Opus 5.5. Browser-use integrations that already use the toolset need no change for this break.&lt;/p&gt;

&lt;p&gt;For accounts created on or after 31 August 2026, a replay that edits the prefix in front of an Opus 5.5 thinking block is the fourth 400. Keep the conversation append-only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calls that succeed and still change behavior
&lt;/h3&gt;

&lt;p&gt;Omitting effort now runs &lt;code&gt;medium&lt;/code&gt;. The same omission on Opus 5 ran &lt;code&gt;high&lt;/code&gt;. At a matched effort, Opus 5.5 tends to think more per turn than Opus 5, especially at &lt;code&gt;xhigh&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;. An effort value copied from an Opus 5 config is a new cost point. Leave room in &lt;code&gt;max_tokens&lt;/code&gt; for the thinking blocks.&lt;/p&gt;

&lt;p&gt;Text written between tool calls comes back inside thinking blocks. At the default display value &lt;code&gt;omitted&lt;/code&gt;, those blocks are empty. A UI that used to stream that text goes quiet, and the request still succeeds. Set a display value when the product needs the progress lines.&lt;/p&gt;

&lt;p&gt;A biology classifier runs beside the cybersecurity classifier. A prompt that pushes the model to copy its reasoning into the visible answer can be declined under &lt;code&gt;reasoning_extraction&lt;/code&gt;. Chart and screenshot reading is sharper before any vision tool is added, according to the prompting guide, so older prompts that used to compensate for weak chart reading are worth replaying on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which workloads to evaluate first
&lt;/h2&gt;

&lt;p&gt;Replay Opus 5.5 on the jobs the launch page is specific about: a multi-file change, a migration with the repository's own tests, a research memo graded against its sources, and a computer-use flow you can run twice. Record the requested model, the model that actually answered, effort, input tokens, cache-read tokens, output tokens, wall time, retries, and human edits. Anthropic's "about 40% less" and "about a fifth of the cost" are claims about their harness. They do not replace that sheet.&lt;/p&gt;

&lt;p&gt;Keep Fable 5.1, or an explicit fallback, on tasks the replay still loses. Plan for Opus 4.8 on traffic the cybersecurity safeguard will hand off, and for Opus 5 on biology and frontier-model-development traffic the safeguard will hand off. Keep Sonnet 5.5 or Sonnet 5 where the task fits a $2 / $10 card and does not need the Opus loop.&lt;/p&gt;

&lt;p&gt;On Modelflare, send &lt;code&gt;claude-opus-5-5&lt;/code&gt; for this tier. The 30 September row enables &lt;code&gt;claude-premium&lt;/code&gt; at 0.5 when a failed call is expensive, &lt;code&gt;claude-stable&lt;/code&gt; at 0.315 for ordinary production, and &lt;code&gt;claude-award&lt;/code&gt; at 0.25 when the call can be retried. &lt;code&gt;claude-tmp-cheap&lt;/code&gt; is not enabled on this model. On &lt;code&gt;claude-stable&lt;/code&gt; the user price is $1.26 per million input tokens and $6.30 per million output tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Launch page, 22 September 2026: &lt;a href="https://www.anthropic.com/claude-opus-5-5" rel="noopener noreferrer"&gt;https://www.anthropic.com/claude-opus-5-5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model page, specs and the current comparison row: &lt;a href="https://platform.claude.com/docs/en/models/opus-5-5/overview" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/models/opus-5-5/overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;What's new, breaking changes and the price card: &lt;a href="https://platform.claude.com/docs/en/models/opus-5-5/whats-new-opus-5-5" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/models/opus-5-5/whats-new-opus-5-5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Claude Code model configuration, default model: &lt;a href="https://code.claude.com/docs/en/model-config" rel="noopener noreferrer"&gt;https://code.claude.com/docs/en/model-config&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modelflare pricing API, read 30 September 2026: &lt;a href="https://modelflare.dev/api/pricing" rel="noopener noreferrer"&gt;https://modelflare.dev/api/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fable 5.1 review on this blog: &lt;a href="https://modelflare.dev/articles/claude-fable-5-1-review-pricing" rel="noopener noreferrer"&gt;Fable 5.1 review&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/claude-opus-5-5-review-pricing?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>claude</category>
      <category>api</category>
    </item>
    <item>
      <title>AI API Latency Metrics: TTFT, First Response, and Output Speed</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Tue, 20 Oct 2026 02:09:35 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/ai-api-latency-metrics-ttft-first-response-and-output-speed-mja</link>
      <guid>https://dev.to/modelflare_dev/ai-api-latency-metrics-ttft-first-response-and-output-speed-mja</guid>
      <description>&lt;p&gt;A request-timeline guide to upstream headers, first SSE event, first effective response, first visible text, end-to-end latency, and output speed.&lt;/p&gt;

&lt;p&gt;AI API latency is not one number. For a streaming request, record when the upstream headers arrive, when the first non-empty SSE event arrives, when the first meaningful content or action appears, when the first visible text appears, and when the response completes. These milestones answer different questions and should not all be labeled “TTFT.”&lt;/p&gt;

&lt;p&gt;For a text-only chat, time to first visible text is usually the user-facing latency. For a reasoning or tool-using workflow, the first effective output can occur earlier as reasoning metadata or function-call arguments. Generation speed is a separate measurement taken after output starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build one request timeline before naming a metric
&lt;/h2&gt;

&lt;p&gt;Use a monotonic clock and one origin for all client- or gateway-level timestamps. A streaming request can be represented as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t0  gateway receives request
t1  gateway starts upstream request
t2  upstream response headers arrive
t3  first non-empty SSE event arrives
t4  first effective content or action arrives
t5  first visible text delta arrives
t6  upstream body completes or closes
t7  gateway handler finishes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every request has every milestone. A non-streaming response has no useful SSE timeline. A function-call-only response may never produce visible text. A cancelled stream may have a start but no normal completion. Store absent values as absent rather than as zero, because zero would falsely imply that the event happened immediately.&lt;/p&gt;

&lt;p&gt;Modelflare records request-level metadata under &lt;code&gt;other.timing&lt;/code&gt;. The fields map to the timeline like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;th&gt;Important boundary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auth_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Token and user authentication duration&lt;/td&gt;
&lt;td&gt;Local phase duration, not upstream time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;distribution_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Channel selection and routing-policy duration&lt;/td&gt;
&lt;td&gt;Local phase duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;body_read_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time spent reading the downstream request body&lt;/td&gt;
&lt;td&gt;Can expose a slow upload before routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_headers_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upstream request start to response headers&lt;/td&gt;
&lt;td&gt;Its origin is upstream start, not initial gateway receipt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_sse_event_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gateway receipt to first non-empty upstream SSE event&lt;/td&gt;
&lt;td&gt;An event can contain metadata rather than useful output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_response_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gateway receipt to first effective content or action delta&lt;/td&gt;
&lt;td&gt;Includes visible text, reasoning summary, function arguments, or custom tool input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_text_delta_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gateway receipt to first visible text delta&lt;/td&gt;
&lt;td&gt;Exists only when visible streaming text appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_done_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gateway receipt to upstream body completion or close&lt;/td&gt;
&lt;td&gt;A close is not automatically a successful completion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_handler_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gateway receipt to the latest known relay completion point&lt;/td&gt;
&lt;td&gt;Closest gateway-side end-to-end duration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visible_output_tps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Output tokens divided by time from first visible text to upstream completion&lt;/td&gt;
&lt;td&gt;A visible-output diagnostic, not system-wide TPS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This mapping prevents a common error: subtracting timestamps that do not share an origin. For example, &lt;code&gt;upstream_headers_ms&lt;/code&gt; is a duration beginning when the upstream request starts, while &lt;code&gt;first_response_ms&lt;/code&gt; begins when the gateway receives the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  TTFT has more than one practical meaning
&lt;/h2&gt;

&lt;p&gt;The conventional definition of time to first token is the duration from query submission to the first received output token. The &lt;a href="https://docs.nvidia.com/nim/benchmarking/llm/latest/metrics.html" rel="noopener noreferrer"&gt;NVIDIA NIM benchmarking guide&lt;/a&gt; notes that this generally includes network latency, queueing, and prompt prefill, and that empty initial responses should not count.&lt;/p&gt;

&lt;p&gt;That definition works for a text-generation benchmark, but modern API streams can emit several things before visible text:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a response-created or metadata event;&lt;/li&gt;
&lt;li&gt;a reasoning summary delta;&lt;/li&gt;
&lt;li&gt;function-call argument fragments;&lt;/li&gt;
&lt;li&gt;custom tool input;&lt;/li&gt;
&lt;li&gt;an empty heartbeat or provider-specific envelope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use explicit names in dashboards and incident reports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Recommended meaning&lt;/th&gt;
&lt;th&gt;Best use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to headers&lt;/td&gt;
&lt;td&gt;Request start to upstream headers&lt;/td&gt;
&lt;td&gt;Network, proxy, and upstream admission diagnosis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first event&lt;/td&gt;
&lt;td&gt;Request start to first non-empty SSE event&lt;/td&gt;
&lt;td&gt;Transport liveness only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first effective response&lt;/td&gt;
&lt;td&gt;Request start to useful content or action&lt;/td&gt;
&lt;td&gt;Agent and reasoning workflow responsiveness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to first visible text&lt;/td&gt;
&lt;td&gt;Request start to text a user can render&lt;/td&gt;
&lt;td&gt;Chat and user-perceived responsiveness&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a chart says only “TTFT,” document which row it means. Otherwise two teams can report different values for the same request and both appear correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate responsiveness from generation speed
&lt;/h2&gt;

&lt;p&gt;End-to-end latency measures the whole request from submission until the full response is received. It includes queueing, prefill, decoding, network transfer, tool pauses represented within the upstream stream, and gateway work visible inside the chosen boundary.&lt;/p&gt;

&lt;p&gt;For a simple completed text stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;end_to_end_latency = final_response_time - request_start_time
visible_generation_window = final_response_time - first_visible_text_time
visible_output_tps = output_tokens / visible_generation_window_seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inter-token latency, also called time per output token, is normally the average spacing between output tokens after the first token. NVIDIA’s AIPerf definition excludes TTFT and divides the remaining duration by &lt;code&gt;output_tokens - 1&lt;/code&gt;. Its reciprocal approximates per-user decode throughput for sufficiently long outputs.&lt;/p&gt;

&lt;p&gt;Do not calculate token-level ITL by counting SSE events or text Delta objects. One event can contain zero, one, or several tokens, and token boundaries depend on the model tokenizer. Modelflare’s &lt;code&gt;visible_output_tps&lt;/code&gt; uses the recorded output-token count and the visible generation window; it does not claim to be a per-token event-spacing measurement or total system throughput.&lt;/p&gt;

&lt;p&gt;These metrics answer separate questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low first-text latency with slow output speed feels responsive at first and then sluggish;&lt;/li&gt;
&lt;li&gt;high first-text latency with fast output speed feels stalled and then finishes quickly;&lt;/li&gt;
&lt;li&gt;low per-user output speed does not prove low system throughput under concurrency;&lt;/li&gt;
&lt;li&gt;high aggregate TPS does not guarantee good latency for one user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reasoning and tools can precede visible text
&lt;/h2&gt;

&lt;p&gt;In a text-only flow, &lt;code&gt;first_response_ms&lt;/code&gt; and &lt;code&gt;first_text_delta_ms&lt;/code&gt; may be nearly identical. In a reasoning or function-calling flow, the gap can be meaningful.&lt;/p&gt;

&lt;p&gt;Suppose a model emits function-call arguments at 1.8 seconds, the application or provider-side tool flow continues, and visible text begins at 6.4 seconds. The system has produced an actionable result at 1.8 seconds, but the user has not seen prose until 6.4 seconds. Calling both values TTFT hides whether the delay occurred before planning, during a tool step, or before answer rendering.&lt;/p&gt;

&lt;p&gt;Interpret the fields according to the product experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for a terminal agent, first effective action may be the best responsiveness signal;&lt;/li&gt;
&lt;li&gt;for a chat UI that displays only prose, first visible text is the relevant user signal;&lt;/li&gt;
&lt;li&gt;for a tool-call API consumed by code, visible text may be absent and should not be required;&lt;/li&gt;
&lt;li&gt;for a reasoning model, an early SSE envelope proves connection progress but not useful progress.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://modelflare.dev/articles/ai-api-streaming-guide" rel="noopener noreferrer"&gt;AI API Streaming Guide&lt;/a&gt; covers SSE parsing, cancellation, and idle-timeout behavior. Latency metrics should be computed only after the parser distinguishes envelopes from effective output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnose the slow stage in a fixed order
&lt;/h2&gt;

&lt;p&gt;Start at the upstream boundary, then move toward local phases. This avoids blaming authentication or routing when the request spent most of its time waiting for the model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Primary metric&lt;/th&gt;
&lt;th&gt;Likely layer to inspect&lt;/th&gt;
&lt;th&gt;Next check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slow before headers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;upstream_headers_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network path, upstream admission, provider queue, proxy route&lt;/td&gt;
&lt;td&gt;Compare channel, region, status, and concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headers are fast, useful output is late&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;first_response_ms&lt;/code&gt; minus early phases&lt;/td&gt;
&lt;td&gt;Model queueing, prompt prefill, reasoning, upstream scheduling&lt;/td&gt;
&lt;td&gt;Compare input tokens, model, route, and inflight count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First event is fast, effective output is late&lt;/td&gt;
&lt;td&gt;Gap from &lt;code&gt;first_sse_event_ms&lt;/code&gt; to &lt;code&gt;first_response_ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Metadata-only events, heartbeats, reasoning startup&lt;/td&gt;
&lt;td&gt;Inspect safe event types, not raw content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective action is fast, visible text is late&lt;/td&gt;
&lt;td&gt;Gap from &lt;code&gt;first_response_ms&lt;/code&gt; to &lt;code&gt;first_text_delta_ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tool/reasoning phase or response composition&lt;/td&gt;
&lt;td&gt;Check requested tools and output mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text begins quickly, then crawls&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;visible_output_tps&lt;/code&gt; and &lt;code&gt;upstream_done_ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Decode throughput, contention, long context, network backpressure&lt;/td&gt;
&lt;td&gt;Compare output length and channel inflight count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All upstream metrics are normal&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auth_ms&lt;/code&gt;, &lt;code&gt;distribution_ms&lt;/code&gt;, &lt;code&gt;body_read_ms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Local auth, policy selection, client upload&lt;/td&gt;
&lt;td&gt;Inspect only the phase that is elevated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No single metric proves a root cause. High &lt;code&gt;upstream_headers_ms&lt;/code&gt;, for example, groups several possibilities that require route, region, provider, and concurrent-load evidence to separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read three synthetic traces
&lt;/h2&gt;

&lt;p&gt;The following values are illustrative diagnostics, not Modelflare production averages or provider benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace A: waiting before upstream headers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_headers_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,100 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_sse_event_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,300 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_response_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,350 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_text_delta_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,400 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_handler_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9,200 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visible_output_tps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of the wait happens before headers. Generation after visible text is comparatively healthy. Check the selected upstream route, provider admission, network path, region, and concurrent load before optimizing the client renderer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace B: useful action before visible prose
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_headers_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;240 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_sse_event_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;310 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_response_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,900 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_text_delta_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8,700 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_handler_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10,200 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visible_output_tps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Transport is alive early and an effective action appears at 2.9 seconds, but visible text waits another 5.8 seconds. If the request uses reasoning or tools, inspect that phase. Increasing a header timeout would not address this pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace C: fast start, slow generation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upstream_headers_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;260 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_sse_event_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;330 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_response_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;420 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_text_delta_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;430 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_handler_ms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20,430 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visible_output_tps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The request becomes visible quickly but spends about 20 seconds generating. Compare output length, context length, selected channel, inflight count, and provider behavior. A first-output timeout would pass and therefore cannot detect this failure mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare latency only under controlled conditions
&lt;/h2&gt;

&lt;p&gt;A fair comparison holds the workload contract stable. At minimum, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact model identifier and route or group;&lt;/li&gt;
&lt;li&gt;input-token and output-token distributions, not just averages;&lt;/li&gt;
&lt;li&gt;streaming versus non-streaming mode;&lt;/li&gt;
&lt;li&gt;enabled reasoning effort and tools;&lt;/li&gt;
&lt;li&gt;region and client-to-gateway network path;&lt;/li&gt;
&lt;li&gt;concurrency or arrival rate;&lt;/li&gt;
&lt;li&gt;sampling settings and maximum output length;&lt;/li&gt;
&lt;li&gt;warmup policy, retry policy, and excluded failures;&lt;/li&gt;
&lt;li&gt;sample size, time window, and percentile method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare p50, p95, and p99 rather than presenting one average. Keep failures visible: removing timeouts and errors can make a route look faster precisely when it is less reliable. Never compare two providers with different prompts, output lengths, concurrency, or endpoint behavior and present the result as a model-speed ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retain diagnostic metadata without retaining content
&lt;/h2&gt;

&lt;p&gt;Latency diagnosis does not require storing prompts, responses, API keys, or plaintext client identities. A useful request record can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a generated request ID and timestamp;&lt;/li&gt;
&lt;li&gt;model, group, and selected channel reference;&lt;/li&gt;
&lt;li&gt;status and terminal outcome;&lt;/li&gt;
&lt;li&gt;input, output, and cached-token counts when available;&lt;/li&gt;
&lt;li&gt;the timing fields above;&lt;/li&gt;
&lt;li&gt;stream event and text-delta counts;&lt;/li&gt;
&lt;li&gt;channel inflight count;&lt;/li&gt;
&lt;li&gt;coarse region and a privacy-reviewed network trace identifier;&lt;/li&gt;
&lt;li&gt;a coarse classifier such as &lt;code&gt;upstream_headers_slow&lt;/code&gt; or &lt;code&gt;generation_slow_tps&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply retention limits and access controls even to metadata. Request IDs, route choices, timing patterns, and token counts can still reveal operational behavior when combined.&lt;/p&gt;

&lt;p&gt;Begin an investigation with the request timeline, not a single TTFT label. Then use &lt;a href="https://modelflare.dev/articles/reliable-ai-api-routing" rel="noopener noreferrer"&gt;Reliable AI API Routing&lt;/a&gt; to compare the exact route selected for each attempt and &lt;a href="https://modelflare.dev/articles/ai-api-error-troubleshooting" rel="noopener noreferrer"&gt;AI API Error Troubleshooting&lt;/a&gt; to pair timing with the terminal status. This produces evidence that can separate admission delay, model startup, reasoning or tool work, visible generation, and local gateway overhead.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/ai-api-latency-metrics?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>performance</category>
      <category>observability</category>
    </item>
    <item>
      <title>AI API Fallback Strategy: Build a Provider Failure Matrix</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:18:55 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/ai-api-fallback-strategy-build-a-provider-failure-matrix-23d6</link>
      <guid>https://dev.to/modelflare_dev/ai-api-fallback-strategy-build-a-provider-failure-matrix-23d6</guid>
      <description>&lt;p&gt;A phase-aware policy for deciding when to retry, use a same-contract route fallback, stop, reconcile side effects, or investigate a provider path.&lt;/p&gt;

&lt;p&gt;An AI API retry, a route fallback, and a model substitution are three different actions. A retry repeats an attempt under the same contract. A route fallback sends the requested model and protocol to another eligible channel or group. A model substitution deliberately changes the requested model and may change quality, price, latency, tool behavior, context limits, and output format.&lt;/p&gt;

&lt;p&gt;A reliable policy decides which action is allowed before an incident. The decision must consider the failure class, whether any response has already reached the caller, whether replay is idempotent, and how many attempts fit within the user’s deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate retry, fallback, and model substitution
&lt;/h2&gt;

&lt;p&gt;Use distinct names in configuration, logs, and runbooks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;What changes&lt;/th&gt;
&lt;th&gt;Appropriate purpose&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same-route retry&lt;/td&gt;
&lt;td&gt;Time and attempt number&lt;/td&gt;
&lt;td&gt;Recover from a short transient failure on the same route&lt;/td&gt;
&lt;td&gt;Repeating load against an unhealthy dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same-contract fallback&lt;/td&gt;
&lt;td&gt;Upstream channel, account, or explicitly ordered group&lt;/td&gt;
&lt;td&gt;Preserve the requested model and protocol when one path fails&lt;/td&gt;
&lt;td&gt;Hidden incompatibility between supposedly equivalent routes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model substitution&lt;/td&gt;
&lt;td&gt;Model identifier or a named model policy&lt;/td&gt;
&lt;td&gt;A product-approved quality, cost, or availability tradeoff&lt;/td&gt;
&lt;td&gt;Silent behavior and billing changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not label all three as “retry.” Operators need to know whether a request was replayed, moved to another path, or answered by a different model. Users need model substitution to be an explicit product contract, not an invisible recovery shortcut.&lt;/p&gt;

&lt;p&gt;Some gateways support ordered model or provider steps. For example, &lt;a href="https://developers.cloudflare.com/ai-gateway/configuration/fallbacks/" rel="noopener noreferrer"&gt;Cloudflare’s fallback documentation&lt;/a&gt; exposes which step produced the successful response. The important design lesson is not to copy a vendor’s exact policy; it is to preserve attempt-level evidence whenever a route changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply four gates before replaying a request
&lt;/h2&gt;

&lt;p&gt;A status code alone is not a retry policy. Evaluate four gates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Failure class:&lt;/strong&gt; Is the failure transient, permanent, caller-caused, or ambiguous?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response phase:&lt;/strong&gt; Did it happen before headers, before effective output, or after output was delivered?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; Can the complete application operation be repeated without a duplicate side effect?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attempt budget:&lt;/strong&gt; Is there enough remaining wall-clock time and an unused attempt?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://docs.cloud.google.com/storage/docs/retry-strategy" rel="noopener noreferrer"&gt;Google Cloud retry strategy&lt;/a&gt; makes the same two foundational distinctions for general APIs: the response indicates whether retry may be useful, and idempotency determines whether replay is safe. It lists &lt;code&gt;408&lt;/code&gt;, &lt;code&gt;429&lt;/code&gt;, &lt;code&gt;5xx&lt;/code&gt;, socket timeouts, and disconnects as commonly transient, while warning that non-idempotent operations need stronger conditions.&lt;/p&gt;

&lt;p&gt;For an AI workflow, idempotency extends beyond the model HTTP request. A replayed prompt can propose the same email, refund, deployment, or database write again. Tool execution therefore needs its own stable idempotency key and persisted result, even if the inference call itself is read-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from a failure matrix
&lt;/h2&gt;

&lt;p&gt;The matrix below is a conservative application policy. A gateway may perform an internal same-contract channel failover before the application sees the terminal result, so coordinate the two layers rather than duplicating them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure or phase&lt;/th&gt;
&lt;th&gt;Same-route retry&lt;/th&gt;
&lt;th&gt;Same-contract fallback&lt;/th&gt;
&lt;th&gt;Stop or investigate&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client validation error, unsupported field, or malformed request&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Fix request&lt;/td&gt;
&lt;td&gt;Replaying the same invalid contract cannot succeed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway authentication, authorization, quota, or policy denial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Stop and correct account or policy&lt;/td&gt;
&lt;td&gt;Another provider route must not bypass the gateway decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upstream credential or account failure before output&lt;/td&gt;
&lt;td&gt;No on the failed route&lt;/td&gt;
&lt;td&gt;Yes, if another verified channel is eligible&lt;/td&gt;
&lt;td&gt;Quarantine and investigate failed channel&lt;/td&gt;
&lt;td&gt;The user contract can remain the same while an unhealthy credential is removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network failure or &lt;code&gt;408&lt;/code&gt; before response output&lt;/td&gt;
&lt;td&gt;At most one bounded attempt if idempotent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Stop at deadline&lt;/td&gt;
&lt;td&gt;The result is transient, but completion may be ambiguous after a disconnect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;429&lt;/code&gt; before output&lt;/td&gt;
&lt;td&gt;Honor &lt;code&gt;Retry-After&lt;/code&gt; for delayed retry&lt;/td&gt;
&lt;td&gt;Yes when another same-contract route has capacity&lt;/td&gt;
&lt;td&gt;Stop when budget is exhausted&lt;/td&gt;
&lt;td&gt;Immediate repeated retries amplify rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upstream &lt;code&gt;500&lt;/code&gt;, &lt;code&gt;502&lt;/code&gt;, &lt;code&gt;503&lt;/code&gt;, or &lt;code&gt;504&lt;/code&gt; before output&lt;/td&gt;
&lt;td&gt;Bounded retry with backoff&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Investigate repeated route failures&lt;/td&gt;
&lt;td&gt;Usually transient, but not evidence that every route is safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema-invalid or malformed provider response before downstream output&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;td&gt;Only to a route verified for the same schema&lt;/td&gt;
&lt;td&gt;Quarantine or investigate compatibility&lt;/td&gt;
&lt;td&gt;Repeating the same incompatible implementation is unlikely to help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model refusal or policy-safe completion&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Return the modeled outcome&lt;/td&gt;
&lt;td&gt;A valid refusal is not an infrastructure outage to route around&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caller cancellation or downstream &lt;code&gt;499&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Stop immediately&lt;/td&gt;
&lt;td&gt;The caller no longer wants work; retries waste capacity and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial stream after visible content or tool arguments reached the caller&lt;/td&gt;
&lt;td&gt;No transparent replay&lt;/td&gt;
&lt;td&gt;No transparent fallback&lt;/td&gt;
&lt;td&gt;Mark partial and let the application decide&lt;/td&gt;
&lt;td&gt;A second stream can duplicate or contradict already delivered output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool side effect has unknown completion state&lt;/td&gt;
&lt;td&gt;No until reconciled&lt;/td&gt;
&lt;td&gt;No until reconciled&lt;/td&gt;
&lt;td&gt;Query the idempotency record or downstream system&lt;/td&gt;
&lt;td&gt;Re-inference can propose the same side effect again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This matrix is deliberately phase-aware. A &lt;code&gt;503&lt;/code&gt; received before any downstream output is different from a connection that closes after 400 text tokens have already been rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat a started stream as a commit boundary
&lt;/h2&gt;

&lt;p&gt;Before downstream output starts, a gateway can often discard a failed attempt and try another eligible route without exposing two answers. After the first byte of meaningful output reaches the caller, transparent replay becomes unsafe.&lt;/p&gt;

&lt;p&gt;A restarted stream may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeat the beginning of an answer;&lt;/li&gt;
&lt;li&gt;produce a different continuation;&lt;/li&gt;
&lt;li&gt;issue a duplicate function call with a new Call ID;&lt;/li&gt;
&lt;li&gt;change usage and cost without a clear boundary;&lt;/li&gt;
&lt;li&gt;leave the client unable to tell which events belong together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a stream breaks after output has started, return a terminal partial or transport error with the original request identity. The application can offer an explicit “try again” action, continue from a safe application checkpoint, or discard the partial output. It should not splice a new model stream onto the old stream as if nothing happened.&lt;/p&gt;

&lt;p&gt;For Function Calling, the safest boundary is stricter: persist accepted Tool Call identities and side-effect results before any retry can recreate them. The &lt;a href="https://modelflare.dev/articles/function-calling-responses-vs-chat-completions" rel="noopener noreferrer"&gt;Function Calling comparison&lt;/a&gt; shows how Call IDs and application idempotency fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bound backoff by attempts and wall-clock time
&lt;/h2&gt;

&lt;p&gt;Exponential backoff spreads repeated attempts over time. Jitter prevents many clients from retrying in lockstep after a shared outage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delay_cap = min(max_delay, base_delay * 2^retry_index)
sleep_for = random_between(0, delay_cap)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honor a valid &lt;code&gt;Retry-After&lt;/code&gt; signal when it fits the user’s deadline. Backoff is not permission to retry: the failure and idempotency gates must pass first.&lt;/p&gt;

&lt;p&gt;Define a total budget, not only a retry count:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum attempts for one user action;&lt;/li&gt;
&lt;li&gt;maximum elapsed time including queueing and backoff;&lt;/li&gt;
&lt;li&gt;maximum attempts before and after selecting a fallback group;&lt;/li&gt;
&lt;li&gt;minimum time left to produce a useful answer;&lt;/li&gt;
&lt;li&gt;cancellation propagation from caller to every active attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an interactive request with a 15-second deadline, three 10-second attempts are not a real policy. The later attempts cannot finish within the user contract. Batch workloads can use a longer budget, but should still have a terminal deadline and a durable job identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevent retry amplification across layers
&lt;/h2&gt;

&lt;p&gt;Assume an SDK makes one request plus two retries: three client attempts. The gateway makes one primary attempt plus two channel fallbacks for each client attempt: three gateway attempts. An upstream proxy makes one retry per attempt: two provider calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 client attempts × 3 gateway attempts × 2 upstream attempts = 18 provider calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One user action has now become 18 provider calls. During an outage, this increases queueing, rate limiting, cost, and recovery time.&lt;/p&gt;

&lt;p&gt;Assign retry ownership deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the gateway owns immediate same-contract channel failover;&lt;/li&gt;
&lt;li&gt;the application owns whether a complete user action may be repeated;&lt;/li&gt;
&lt;li&gt;an SDK’s automatic retries are disabled or bounded when the gateway already retries;&lt;/li&gt;
&lt;li&gt;asynchronous jobs use one durable job ID and an attempt ledger;&lt;/li&gt;
&lt;li&gt;no layer starts a new attempt after caller cancellation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Log both “attempt number at this layer” and a stable end-to-end request identity. Otherwise each layer appears to have made only two or three attempts while the combined amplification remains hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify that a fallback really preserves the contract
&lt;/h2&gt;

&lt;p&gt;The same public model name does not prove two routes behave identically. Before adding a channel to a same-contract fallback set, test:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract area&lt;/th&gt;
&lt;th&gt;Evidence required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model identity&lt;/td&gt;
&lt;td&gt;Requested model remains available without silent mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Endpoint&lt;/td&gt;
&lt;td&gt;Responses or Chat Completions request is accepted as configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Event types, termination, usage, and cancellation are handled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured output&lt;/td&gt;
&lt;td&gt;Required JSON Schema subset and strict behavior work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function calling&lt;/td&gt;
&lt;td&gt;Tool definitions, Call IDs, argument streaming, and results round-trip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Limits&lt;/td&gt;
&lt;td&gt;Context, output, rate, and concurrency limits fit the workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors&lt;/td&gt;
&lt;td&gt;Status and error bodies remain classifiable without exposing secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage and cost&lt;/td&gt;
&lt;td&gt;Token accounting, cache fields, service tier, and price policy are understood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety and region&lt;/td&gt;
&lt;td&gt;Required policy, data path, and residency constraints remain satisfied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a candidate route fails one required area, it is not a transparent fallback for that workload. It may still be usable under a separate, explicit product policy.&lt;/p&gt;

&lt;p&gt;Changing to another model is always such a policy decision. Define the permitted model, quality floor, price ceiling, Tool Contract, and user-visible disclosure. Do not use a cheaper or weaker model merely because a route returned an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand Modelflare’s current fallback semantics
&lt;/h2&gt;

&lt;p&gt;Modelflare searches eligible paths for the model requested by the API client. A regular API Key has one primary group and can have an ordered list of fallback groups. A Smart API Key evaluates groups available to the account using its configured routing strategy. Neither mechanism should silently replace the requested model with another model.&lt;/p&gt;

&lt;p&gt;Group-level RPM admission occurs before billing and before an upstream request. If the selected group is full, an ordered fallback group or Smart Routing candidate can be evaluated; if no eligible group remains, the request returns &lt;code&gt;429&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Within a selected group, channel priority defines account failover order. After an upstream error, the failed channel is excluded and selection continues through remaining eligible channels. Current channel failover is independent of &lt;code&gt;RetryTimes&lt;/code&gt; and &lt;code&gt;AutomaticRetryStatusCodes&lt;/code&gt;. It stops on success, eligible-route exhaustion, caller cancellation, or after a downstream response has started and can no longer be transparently replayed.&lt;/p&gt;

&lt;p&gt;These are internal same-model path decisions, not permission for a client to add another unlimited retry loop. Use &lt;a href="https://modelflare.dev/articles/reliable-ai-api-routing" rel="noopener noreferrer"&gt;Reliable AI API Routing&lt;/a&gt; for group and channel design, and &lt;a href="https://modelflare.dev/articles/ai-api-error-troubleshooting" rel="noopener noreferrer"&gt;AI API Error Troubleshooting&lt;/a&gt; to distinguish gateway policy errors from upstream failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve evidence for every attempt
&lt;/h2&gt;

&lt;p&gt;A final &lt;code&gt;200&lt;/code&gt; does not prove that the first route succeeded. A final channel ID does not describe failed attempts. Retain enough metadata to reconstruct the path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable request ID and caller-visible correlation ID;&lt;/li&gt;
&lt;li&gt;attempt sequence and selected group/channel reference;&lt;/li&gt;
&lt;li&gt;model and endpoint contract used for each attempt;&lt;/li&gt;
&lt;li&gt;failure status, error class, and stream phase;&lt;/li&gt;
&lt;li&gt;whether downstream output had started;&lt;/li&gt;
&lt;li&gt;timing milestones and cancellation state;&lt;/li&gt;
&lt;li&gt;input, output, and cached-token usage when available;&lt;/li&gt;
&lt;li&gt;cost attribution by completed or billable attempt;&lt;/li&gt;
&lt;li&gt;final terminal reason: success, exhausted, cancelled, partial, or policy stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not retain API keys, raw prompts, raw responses, or provider credentials merely to diagnose fallback. Redacted error classes and timing metadata are usually sufficient, with restricted short-lived request archives reserved for explicitly configured failure investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drill the policy before production traffic depends on it
&lt;/h2&gt;

&lt;p&gt;Run a staging exercise using the real protocol boundary and safe deterministic inputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;make the primary channel unavailable before headers and verify the next eligible same-model path;&lt;/li&gt;
&lt;li&gt;return a rate limit and verify attempt limits and any &lt;code&gt;Retry-After&lt;/code&gt; handling;&lt;/li&gt;
&lt;li&gt;cancel the caller and prove that no later attempt starts;&lt;/li&gt;
&lt;li&gt;break a stream after output and prove that no transparent replay occurs;&lt;/li&gt;
&lt;li&gt;send an invalid request and prove that fallback does not hide it;&lt;/li&gt;
&lt;li&gt;repeat a tool workflow and prove that one side effect is recorded;&lt;/li&gt;
&lt;li&gt;exhaust every route and verify one clear terminal error;&lt;/li&gt;
&lt;li&gt;inspect the attempt ledger and reconcile usage and cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Roll out the policy to a small workload, monitor attempt count and success-after-fallback separately from raw success rate, and keep a fast way to remove an unhealthy route. The goal is not to maximize fallback frequency. It is to recover only when the original model contract can be preserved safely, within a bounded deadline, with evidence for every attempt.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/ai-api-fallback-matrix?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>reliability</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Structured Outputs with OpenAI-Compatible APIs: A JSON Schema Guide</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Fri, 16 Oct 2026 01:45:19 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/structured-outputs-with-openai-compatible-apis-a-json-schema-guide-46bb</link>
      <guid>https://dev.to/modelflare_dev/structured-outputs-with-openai-compatible-apis-a-json-schema-guide-46bb</guid>
      <description>&lt;p&gt;A field-level guide to strict JSON Schema outputs across Responses and Chat Completions, including validation layers, failure handling, and route compatibility tests.&lt;/p&gt;

&lt;p&gt;Structured Outputs let an application request JSON that follows a defined schema instead of merely asking the model to “return JSON.” With an OpenAI-compatible API, the same schema can be used through Responses or Chat Completions, but the request fields are different—and compatibility must be verified for the selected model and route.&lt;/p&gt;

&lt;p&gt;The safe production pattern has three layers: use &lt;code&gt;strict&lt;/code&gt; schema output when the model supports it, parse and validate the returned JSON in the application, and then apply business rules to the values. Schema compliance removes many formatting failures; it does not prove that the model’s answer is factually or semantically correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured Outputs are not the same as JSON mode
&lt;/h2&gt;

&lt;p&gt;There are three common ways to ask a model for JSON-like output.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Produces valid JSON&lt;/th&gt;
&lt;th&gt;Enforces your schema&lt;/th&gt;
&lt;th&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt only&lt;/td&gt;
&lt;td&gt;Not guaranteed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Prototypes where parsing failures are acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON mode&lt;/td&gt;
&lt;td&gt;Yes, when supported and completed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Flexible JSON where the application validates the shape&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured Outputs with &lt;code&gt;strict: true&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes, subject to completion and refusal handling&lt;/td&gt;
&lt;td&gt;Yes, within the supported JSON Schema subset&lt;/td&gt;
&lt;td&gt;Typed extraction and application workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://developers.openai.com/api/docs/guides/structured-outputs" rel="noopener noreferrer"&gt;OpenAI Structured Outputs guide&lt;/a&gt; recommends Structured Outputs instead of JSON mode when the selected model and endpoint support it. The same guide also distinguishes two different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a structured response format when the model should answer the user with a predictable data object.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;function calling&lt;/a&gt; when the model should request an action from your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focuses on the first job. A later tool loop can reuse strict argument schemas, but its call IDs and result messages follow a different contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define one schema before choosing the endpoint
&lt;/h2&gt;

&lt;p&gt;Consider a support-triage workflow. The application needs four fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;category&lt;/code&gt;: one of &lt;code&gt;billing&lt;/code&gt;, &lt;code&gt;technical&lt;/code&gt;, or &lt;code&gt;account&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;priority&lt;/code&gt;: an integer from 1 through 3;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requires_human&lt;/code&gt;: a boolean;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt;: a short description for the support queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JSON Schema is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"technical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"account"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maximum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_human"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"requires_human"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"summary"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Making every property required and setting &lt;code&gt;additionalProperties&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; produces a stable object for downstream code. It does not make every possible JSON Schema keyword portable. OpenAI supports a documented subset, and another OpenAI-compatible provider may support a different subset or no strict schema mode at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send the schema with the Responses API
&lt;/h2&gt;

&lt;p&gt;The Responses API places the response schema under &lt;code&gt;text.format&lt;/code&gt;. Set a Modelflare API key and choose a model that is currently listed for your API key and has been verified for Structured Outputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR_API_KEY&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT&amp;gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://modelflare.dev/v1/responses &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL_ID&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;The customer was charged twice and wants a refund.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
      &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;json_schema&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;support_ticket&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;properties&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;billing&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;technical&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;account&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]
            },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;minimum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;maximum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 3
            },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;requires_human&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; }
          },
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;requires_human&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;],
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;additionalProperties&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: false
        },
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true
      }
    }
  }"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a Responses response, generated content appears in typed output items. SDKs may expose convenience helpers such as aggregated output text or parsed output, but an HTTP client should not assume that the structured object is a top-level field. Read the completed response object, locate its text output, then parse that text as JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send the same schema with Chat Completions
&lt;/h2&gt;

&lt;p&gt;Chat Completions places the response schema under &lt;code&gt;response_format.json_schema&lt;/code&gt;. The schema body is unchanged; only the wrapper differs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://modelflare.dev/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL_ID&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;messages&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [
      {
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;The customer was charged twice and wants a refund.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;
      }
    ],
    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;response_format&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
      &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;json_schema&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
      &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;json_schema&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;support_ticket&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;schema&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;properties&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;billing&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;technical&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;account&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]
            },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;minimum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1,
              &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;maximum&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 3
            },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;requires_human&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; },
            &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; }
          },
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;priority&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;requires_human&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;summary&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;],
          &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;additionalProperties&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: false
        },
        &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true
      }
    }
  }"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Chat Completions, the JSON text is normally returned in &lt;code&gt;choices[0].message.content&lt;/code&gt;. Parse the content rather than treating the string as an already typed object.&lt;/p&gt;

&lt;p&gt;The two wrappers can be summarized as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Responses API&lt;/th&gt;
&lt;th&gt;Chat Completions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schema container&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format.json_schema&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.format.type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format.type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.format.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format.json_schema.name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema body&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.format.schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format.json_schema.schema&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strict flag&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text.format.strict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;response_format.json_schema.strict&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result location&lt;/td&gt;
&lt;td&gt;Typed output items / output text helper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;choices[0].message.content&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Modelflare’s OpenAI compatibility layer has explicit conversion logic for these two wrappers on supported OpenAI/Codex routes. That conversion does not create Structured Outputs support in an upstream model that lacks it. For other model families served as raw Chat Completions pass-through, the upstream provider’s exact request contract remains the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate structure and meaning separately
&lt;/h2&gt;

&lt;p&gt;Suppose the model returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requires_human"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer reports a duplicate charge and requests a refund."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This object satisfies the schema, but the application should still perform two distinct checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structural validation
&lt;/h3&gt;

&lt;p&gt;Use a JSON Schema validator or a typed SDK helper to confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the object contains every required property;&lt;/li&gt;
&lt;li&gt;no unexpected property is present;&lt;/li&gt;
&lt;li&gt;each value has the required type;&lt;/li&gt;
&lt;li&gt;the category is in the allowed enum;&lt;/li&gt;
&lt;li&gt;the priority is within the permitted range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even when the provider promises strict adherence, application-side validation protects the rest of the system from unsupported routes, integration mistakes, truncated content, and future contract changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic and business validation
&lt;/h3&gt;

&lt;p&gt;The schema cannot determine whether the customer was actually charged twice, whether priority &lt;code&gt;2&lt;/code&gt; is correct, or whether a human must approve a refund. Those decisions require source data and application policy.&lt;/p&gt;

&lt;p&gt;For high-impact workflows, treat the model object as a proposed classification. Compare it with authoritative records, enforce permissions and financial limits in deterministic code, and retain a safe audit reference. Never let a structurally valid model object bypass an authorization, billing, or security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handle incomplete and exceptional output
&lt;/h2&gt;

&lt;p&gt;A production parser needs more than a happy-path &lt;code&gt;JSON.parse&lt;/code&gt; call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refusals
&lt;/h3&gt;

&lt;p&gt;A model may refuse a request for safety reasons. OpenAI’s structured-output contract makes refusals distinguishable from normal schema output. Check the response status and refusal representation before looking for the JSON object; do not report a refusal as a schema parsing error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Truncation and output limits
&lt;/h3&gt;

&lt;p&gt;If generation stops before the object is complete, no schema can repair the missing suffix. Check the endpoint’s completion status and stop reason. Set an output limit that is sufficient for the largest valid object, while keeping the schema and requested content bounded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unsupported schema keywords
&lt;/h3&gt;

&lt;p&gt;Strict output implementations commonly support only a subset of JSON Schema. Start with objects, arrays, primitive types, enums, required fields, and explicit additional-property behavior. Check the current provider documentation before using references, recursive shapes, complex unions, or advanced validation keywords.&lt;/p&gt;

&lt;h3&gt;
  
  
  First-use schema latency
&lt;/h3&gt;

&lt;p&gt;Some providers preprocess and cache a new schema. The first request for a new schema can therefore take longer than later requests. Reuse stable, versioned schemas instead of generating a unique schema for every request, and separate first-use measurements from steady-state latency comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model or route incompatibility
&lt;/h3&gt;

&lt;p&gt;An OpenAI-compatible endpoint can accept ordinary Chat Completions while rejecting &lt;code&gt;json_schema&lt;/code&gt;, ignoring &lt;code&gt;strict&lt;/code&gt;, or forwarding the fields to a model that does not support them. A &lt;code&gt;200&lt;/code&gt; response containing JSON is still not proof of strict adherence. Test invalid and edge-case inputs and validate the resulting object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a compatibility test before production
&lt;/h2&gt;

&lt;p&gt;Use a small matrix for every model and route you intend to enable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Evidence to record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimal required object&lt;/td&gt;
&lt;td&gt;Status, model, route, parsed object, validation result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every enum value&lt;/td&gt;
&lt;td&gt;Whether each allowed value can be produced and parsed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing information&lt;/td&gt;
&lt;td&gt;Whether the model uses a bounded fallback value or asks for clarification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety-triggering input&lt;/td&gt;
&lt;td&gt;Refusal representation and application handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low output limit&lt;/td&gt;
&lt;td&gt;Completion status and truncation handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsupported schema feature&lt;/td&gt;
&lt;td&gt;Explicit error versus silently ignored constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Responses and Chat wrappers&lt;/td&gt;
&lt;td&gt;Whether both produce equivalent application objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated stable schema&lt;/td&gt;
&lt;td&gt;First-use versus steady-state timing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not compare models with different schemas, prompts, regions, streaming modes, or output limits and then attribute the result only to model quality. Record the exact test date because provider behavior and model availability can change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the endpoint after testing the workflow
&lt;/h2&gt;

&lt;p&gt;Use Responses when the application already works with typed output items, Responses streaming events, or a broader Responses tool workflow. Use Chat Completions when the application has a stable message-based integration and the selected route supports its &lt;code&gt;response_format&lt;/code&gt; contract. The broader decision is covered in &lt;a href="https://modelflare.dev/articles/responses-api-vs-chat-completions" rel="noopener noreferrer"&gt;Responses API vs Chat Completions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whichever endpoint you select:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;keep one versioned source for the JSON Schema;&lt;/li&gt;
&lt;li&gt;translate that schema into the endpoint’s wrapper without changing its meaning;&lt;/li&gt;
&lt;li&gt;validate the completed response before using it;&lt;/li&gt;
&lt;li&gt;apply deterministic business rules after structural validation;&lt;/li&gt;
&lt;li&gt;monitor parsing, refusal, truncation, and compatibility failures separately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For initial client migration, start with the &lt;a href="https://modelflare.dev/articles/openai-compatible-api-guide" rel="noopener noreferrer"&gt;OpenAI-Compatible API Guide&lt;/a&gt;. For event parsing and incomplete streams, use the &lt;a href="https://modelflare.dev/articles/ai-api-streaming-guide" rel="noopener noreferrer"&gt;AI API Streaming Guide&lt;/a&gt;. Check &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Models &amp;amp; Pricing&lt;/a&gt; for the current model and group surface, then verify Structured Outputs on the exact route your API key will use.&lt;/p&gt;

&lt;p&gt;Structured Outputs are most useful when they narrow a model response into a dependable application interface. They are not a substitute for factual verification, authorization, or billing logic—and “OpenAI-compatible” remains a claim to test feature by feature, not a guarantee to infer from the Base URL.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/structured-outputs-openai-compatible-api?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>llm</category>
      <category>json</category>
      <category>api</category>
    </item>
    <item>
      <title>One Key, a Dozen-Plus Models: Switching LLMs in VS Code Chat with Modelflare</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:32:00 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/one-key-a-dozen-plus-models-switching-llms-in-vs-code-chat-with-modelflare-4pi1</link>
      <guid>https://dev.to/modelflare_dev/one-key-a-dozen-plus-models-switching-llms-in-vs-code-chat-with-modelflare-4pi1</guid>
      <description>&lt;p&gt;How to use the Modelflare VS Code extension to put every text model your account can reach into the VS Code chat model picker, behind a single Smart API Key.&lt;/p&gt;

&lt;p&gt;Most developers no longer use one model. A fast, cheap model is fine for "rename this variable everywhere." A frontier model earns its price on a gnarly refactor. A third model is the one you trust for reviewing a migration. The problem is that each of those usually lives behind its own extension, its own account, and its own key.&lt;/p&gt;

&lt;p&gt;That setup has real costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration sprawl.&lt;/strong&gt; Three providers means three keys in three places, three billing dashboards, and three sets of rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switching is slow.&lt;/strong&gt; If changing models means changing extensions, you stop doing it, and you end up paying frontier prices for trivial edits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New models lag.&lt;/strong&gt; Every release means waiting for a provider extension update or editing config by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outages are your problem.&lt;/strong&gt; When one provider has a bad hour, the chat panel just errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VS Code already has the right UI for this: the chat model picker. What it needs is one provider that can fill that picker with models from many vendors. That is what the Modelflare extension does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the extension does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=modelflare.modelflare-vscode" rel="noopener noreferrer"&gt;Modelflare for VS Code&lt;/a&gt; registers Modelflare as a language model provider for VS Code chat. According to its &lt;a href="https://github.com/castaly-ai/modelflare-vscode" rel="noopener noreferrer"&gt;README&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text models available to your key are loaded from &lt;code&gt;GET /v1/models&lt;/code&gt;, so new models appear without editing the key.&lt;/li&gt;
&lt;li&gt;Responses stream, and tool calling works for agent mode.&lt;/li&gt;
&lt;li&gt;Routing across groups, fallback and the routing strategy are handled server-side by the Smart API Key.&lt;/li&gt;
&lt;li&gt;The key lives in VS Code's secret storage, never in &lt;code&gt;settings.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It is bring-your-own-key: no Copilot subscription is required for BYOK models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extension ID is &lt;code&gt;modelflare.modelflare-vscode&lt;/code&gt;, and it requires VS Code 1.104 or newer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Smart API Key
&lt;/h2&gt;

&lt;p&gt;This is the piece that makes "one key" true. An ordinary Modelflare key is bound to a single routing group and can only call that group's models. A &lt;a href="https://docs.modelflare.dev/guides/features/smart-api-key/" rel="noopener noreferrer"&gt;Smart API Key&lt;/a&gt; is bound to &lt;code&gt;auto&lt;/code&gt;. On every request Modelflare:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;collects every routing group your account may use,&lt;/li&gt;
&lt;li&gt;keeps only the groups that currently have a channel for the model you asked for,&lt;/li&gt;
&lt;li&gt;orders them by the key's strategy, and&lt;/li&gt;
&lt;li&gt;sends to the first candidate, moving to the next group if that one runs out of usable channels.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So a model that only one group serves still works, and a group that is momentarily unusable is skipped instead of failing your chat.&lt;/p&gt;

&lt;p&gt;To create one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://modelflare.dev/sign-up?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=vscode-one-key" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; or open the console.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://modelflare.dev/keys" rel="noopener noreferrer"&gt;API Keys&lt;/a&gt; â†’ &lt;strong&gt;Create Smart API Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pick a routing strategy:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Order of candidate groups&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Balanced (default)&lt;/td&gt;
&lt;td&gt;Starts in the middle of the price range, works up to the most expensive candidate, then back down through the cheaper ones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stability first&lt;/td&gt;
&lt;td&gt;Highest group multiplier first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low price first&lt;/td&gt;
&lt;td&gt;Lowest group multiplier first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can change the strategy on the existing key at any time; the next request uses the new order. Quota, expiry date and IP allowlist work as on any other key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Install the extension
&lt;/h2&gt;

&lt;p&gt;From the VS Code Marketplace, search for &lt;strong&gt;Modelflare&lt;/strong&gt; in the Extensions view, or install from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; modelflare.modelflare-vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same extension is also published on Open VSX, the registry used by VS Code-compatible builds such as VSCodium: &lt;a href="https://open-vsx.org/extension/modelflare/modelflare-vscode" rel="noopener noreferrer"&gt;open-vsx.org/extension/modelflare/modelflare-vscode&lt;/a&gt;. It needs a build based on VS Code 1.104 or newer, the release line with the chat model provider API the extension uses.&lt;/p&gt;

&lt;p&gt;A note for early adopters: version 0.1.0 shipped under the ID &lt;code&gt;modelflare.modelflare&lt;/code&gt;. From 0.1.1 the ID is &lt;code&gt;modelflare.modelflare-vscode&lt;/code&gt;, because the shorter name is taken on the VS Code Marketplace. Commands and settings are unchanged, but VS Code keeps secrets per extension ID, so if you installed 0.1.0, run &lt;strong&gt;Manage Modelflare API Key&lt;/strong&gt; once after updating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add your key
&lt;/h2&gt;

&lt;p&gt;Either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open the chat model picker â†’ &lt;strong&gt;Manage Models&lt;/strong&gt; â†’ &lt;strong&gt;Modelflare&lt;/strong&gt;, or&lt;/li&gt;
&lt;li&gt;run &lt;strong&gt;Modelflare: Manage Modelflare API Key&lt;/strong&gt; from the Command Palette.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste the Smart API Key. It goes into VS Code's secret storage. Then open the model picker again and choose a Modelflare model.&lt;/p&gt;

&lt;p&gt;The extension has exactly one setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"modelflare.baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"global"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;global&lt;/code&gt; (the default) points at &lt;code&gt;https://modelflare.dev/v1&lt;/code&gt;; &lt;code&gt;china&lt;/code&gt; points at &lt;code&gt;https://origin.modelflare.dev/v1&lt;/code&gt;; you can also give a custom URL. Most readers never need to touch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: three models, one task
&lt;/h2&gt;

&lt;p&gt;To show why switching matters, here is a realistic loop on a small TypeScript service. All three models were picked from the same dropdown, on the same key, without leaving the chat panel.&lt;/p&gt;

&lt;p&gt;The model IDs below are real entries in the &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Modelflare catalog&lt;/a&gt;. Each one was also returned by &lt;code&gt;GET /v1/models&lt;/code&gt; for a Smart API Key on the day we wrote this, and that is the same list the extension loads. Which ones appear in your picker depends on what your account can reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Explore with a fast model.&lt;/strong&gt; Pick &lt;code&gt;gemini-3.5-flash&lt;/code&gt; and ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summarize what src/billing/ does and list every place we compute a rounded price.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cheap, quick, and good enough to map a folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Refactor with a frontier model in agent mode.&lt;/strong&gt; Switch the picker to &lt;code&gt;claude-opus-5-5&lt;/code&gt;, switch the chat to agent mode, and ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract the rounding logic you found into src/billing/round.ts, update every caller, and add unit tests.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where streaming plus tool calling matters: the model reads files, proposes edits, and runs through the change set the way agent mode normally does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Get a second opinion.&lt;/strong&gt; Switch to &lt;code&gt;gpt-6.1-sol&lt;/code&gt; and ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review the diff in this workspace. Look for behavior changes in rounding at .5 boundaries.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A different model family reviewing another one's work catches a surprising number of edge cases.&lt;/p&gt;

&lt;p&gt;Three vendors, one key, one bill. Every request shows up in the &lt;a href="https://modelflare.dev/usage-logs" rel="noopener noreferrer"&gt;usage logs&lt;/a&gt;, billed with the multiplier of the group that actually served it, and the log records the resolved group and strategy (&lt;a href="https://docs.modelflare.dev/guides/usage-and-cost/" rel="noopener noreferrer"&gt;Usage and cost&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For reference, here are a dozen-plus text model IDs that were both in the live catalog and in a Smart API Key's &lt;code&gt;GET /v1/models&lt;/code&gt; response on the day we wrote this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Example IDs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI GPT&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-6.1-sol&lt;/code&gt;, &lt;code&gt;gpt-6-sol&lt;/code&gt;, &lt;code&gt;gpt-5.5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Claude&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;claude-opus-5-5&lt;/code&gt;, &lt;code&gt;claude-sonnet-5-5&lt;/code&gt;, &lt;code&gt;claude-haiku-5-5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gemini-3.8-flash&lt;/code&gt;, &lt;code&gt;gemini-3.5-flash&lt;/code&gt;, &lt;code&gt;gemini-3.1-pro-preview&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xAI Grok&lt;/td&gt;
&lt;td&gt;&lt;code&gt;grok-4.7&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deepseek-v4-pro&lt;/code&gt;, &lt;code&gt;deepseek-v4-flash&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Others&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kimi-k3&lt;/code&gt;, &lt;code&gt;glm-5.3&lt;/code&gt;, &lt;code&gt;qwen3.8-max&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Treat this as a snapshot. The picker is the source of truth for your account, and &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Models &amp;amp; Pricing&lt;/a&gt; is the source of truth for prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Refresh after a release.&lt;/strong&gt; When a new model lands, run &lt;strong&gt;Modelflare: Refresh Modelflare Models&lt;/strong&gt;. The key itself needs no edit: a Smart API Key has no per-model allowlist and always follows your account's current access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the strategy, not a second key.&lt;/strong&gt; If you want cheaper defaults for a side project and maximum stability for work, change the strategy, or create two Smart API Keys with different strategies. You do not need provider-specific keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not build your own fallback list.&lt;/strong&gt; A Smart API Key already walks every group your account can use. Ordered fallback chains are for ordinary keys; see &lt;a href="https://docs.modelflare.dev/guides/features/group-failover/" rel="noopener noreferrer"&gt;Group failover&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick the model per task, not per day.&lt;/strong&gt; The cost difference between a flash-class model and a frontier model is large. Defaulting to a fast model and switching up only for the hard step is the habit this setup makes cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read errors literally.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt;: the key is missing or invalid. Create a Smart API Key at &lt;a href="https://modelflare.dev/keys" rel="noopener noreferrer"&gt;modelflare.dev/keys&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;model_not_found&lt;/code&gt;: a regular key only calls its own group's models. If you already use a Smart API Key, that model is not available to your account right now.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403 insufficient_user_quota&lt;/code&gt;: top up at &lt;a href="https://modelflare.dev/billing" rel="noopener noreferrer"&gt;modelflare.dev/billing&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Slow model list from mainland China?&lt;/strong&gt; Set &lt;code&gt;modelflare.baseUrl&lt;/code&gt; to &lt;code&gt;china&lt;/code&gt;. Since 0.1.2, the extension's public catalog request times out after 8 seconds and falls back to the key's own model list, so the picker no longer hangs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want images and video too?&lt;/strong&gt; The VS Code extension covers text models. For media generation in an agent, see the &lt;a href="https://docs.modelflare.dev/guides/clients/media-skill/" rel="noopener noreferrer"&gt;Media Skill&lt;/a&gt; or the &lt;a href="https://www.npmjs.com/package/@modelflare/mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;@modelflare/mcp-server&lt;/code&gt;&lt;/a&gt; package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Install: &lt;a href="https://marketplace.visualstudio.com/items?itemName=modelflare.modelflare-vscode" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt; Â· &lt;a href="https://open-vsx.org/extension/modelflare/modelflare-vscode" rel="noopener noreferrer"&gt;Open VSX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source and changelog: &lt;a href="https://github.com/castaly-ai/modelflare-vscode" rel="noopener noreferrer"&gt;github.com/castaly-ai/modelflare-vscode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Smart API Key: &lt;a href="https://docs.modelflare.dev/guides/features/smart-api-key/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/features/smart-api-key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Models and groups: &lt;a href="https://docs.modelflare.dev/guides/models-and-groups/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/models-and-groups&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Text models: &lt;a href="https://docs.modelflare.dev/models/text/" rel="noopener noreferrer"&gt;docs.modelflare.dev/models/text&lt;/a&gt; Â· Live catalog: &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;modelflare.dev/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Platform features: &lt;a href="https://docs.modelflare.dev/guides/features/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/features&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model picker was always the right place to choose a model. With one key behind it, choosing is finally cheap enough to do on every task.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Generate Images and Videos in Claude Code with One Sentence</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:31:41 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/generate-images-and-videos-in-claude-code-with-one-sentence-5gg8</link>
      <guid>https://dev.to/modelflare_dev/generate-images-and-videos-in-claude-code-with-one-sentence-5gg8</guid>
      <description>&lt;p&gt;How to give Claude Code image and video generation with the Modelflare Claude Code plugin, the Media Skill, or the &lt;code&gt;@modelflare/mcp-server&lt;/code&gt; MCP server, all on one Smart API Key.&lt;/p&gt;

&lt;p&gt;Claude Code is good at writing the landing page. It is not good at producing the hero image for that page, the 5-second product loop in the header, or the twelve icon variants the designer asked for. Those normally mean leaving the terminal: open an image tool, sign in to a video provider, download files, rename them, drag them into &lt;code&gt;public/&lt;/code&gt;, and come back.&lt;/p&gt;

&lt;p&gt;The friction is not the generation itself. It is everything around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every media provider has its own contract.&lt;/strong&gt; OpenAI-compatible image models use &lt;code&gt;POST /v1/images/generations&lt;/code&gt;; Gemini image models use native &lt;code&gt;generateContent&lt;/code&gt;. Sending a model to the wrong endpoint is rejected with &lt;code&gt;unsupported_endpoint&lt;/code&gt; (&lt;a href="https://docs.modelflare.dev/models/image/" rel="noopener noreferrer"&gt;Image models&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video is asynchronous.&lt;/strong&gt; You create a task, poll it, and download the result before the URL expires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices change.&lt;/strong&gt; "Which video model under 0.10 USD per second can do image-to-video?" is a question you should not answer from memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys multiply.&lt;/strong&gt; A separate key per vendor means separate billing, separate quotas, and separate places to leak a secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide removes that loop. By the end, a single sentence in Claude Code produces a file on disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways in, one key
&lt;/h2&gt;

&lt;p&gt;There are three Modelflare integrations that reach Claude Code. They overlap on purpose, so pick the one that matches how you work.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/castaly-ai/modelflare-claude-code" rel="noopener noreferrer"&gt;Claude Code plugin&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A Claude Code plugin marketplace that ships the Modelflare Media Skill&lt;/td&gt;
&lt;td&gt;Claude Code users who want a two-command install and &lt;code&gt;claude plugin update&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.modelflare.dev/guides/clients/media-skill/" rel="noopener noreferrer"&gt;Media Skill&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A folder with a &lt;code&gt;SKILL.md&lt;/code&gt;, a model reference and one helper script&lt;/td&gt;
&lt;td&gt;Any &lt;code&gt;SKILL.md&lt;/code&gt;-aware agent (Claude Code, Codex and others), shared machines, CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/@modelflare/mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;@modelflare/mcp-server&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;An MCP server with explicit &lt;code&gt;list_models&lt;/code&gt;, &lt;code&gt;generate_image&lt;/code&gt;, &lt;code&gt;create_video&lt;/code&gt;, &lt;code&gt;get_video_status&lt;/code&gt; and &lt;code&gt;download_video&lt;/code&gt; tools&lt;/td&gt;
&lt;td&gt;Any MCP client (Claude Desktop, Cursor, Gemini CLI, Claude Code) when you want typed tools instead of a skill&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three use the same credential: a Modelflare &lt;strong&gt;Smart API Key&lt;/strong&gt; in the &lt;code&gt;MODELFLARE_API_KEY&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Smart API Key
&lt;/h2&gt;

&lt;p&gt;An ordinary Modelflare key is bound to one routing group and can only call the models that group serves. A &lt;a href="https://docs.modelflare.dev/guides/features/smart-api-key/" rel="noopener noreferrer"&gt;Smart API Key&lt;/a&gt; is bound to &lt;code&gt;auto&lt;/code&gt;: it can call every model your account has access to and resolves the routing group per request. For media work this matters, because image and video models are rarely all served by the same group.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://modelflare.dev/sign-up?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=claude-code-media" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; or open the console.&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://modelflare.dev/keys" rel="noopener noreferrer"&gt;API Keys&lt;/a&gt; and choose &lt;strong&gt;Create Smart API Key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pick a routing strategy: &lt;strong&gt;Balanced&lt;/strong&gt; (default), &lt;strong&gt;Stability first&lt;/strong&gt;, or &lt;strong&gt;Low price first&lt;/strong&gt;. You can change it on the existing key later; the next request uses the new order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A request is billed with the multiplier of the group that actually served it, and the consume log records the resolved group and strategy (&lt;a href="https://docs.modelflare.dev/guides/usage-and-cost/" rel="noopener noreferrer"&gt;Usage and cost&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Then expose the key to the environment Claude Code runs in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux: add this to your shell profile to keep it&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows PowerShell: the second line makes it persistent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;setx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Media Skill never writes the key into files or prompts and never prints it. Keep it that way on your side: do not paste it into a chat, a commit, or a ticket.&lt;/p&gt;

&lt;p&gt;If Claude Code itself does not run through Modelflare yet and you want it to, the &lt;a href="https://docs.modelflare.dev/guides/clients/claude-code/" rel="noopener noreferrer"&gt;Claude Code setup guide&lt;/a&gt; covers the installer (&lt;code&gt;curl -fsSL https://modelflare.dev/install/claude.sh | sh&lt;/code&gt;) and the manual &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. That is optional: media generation works whatever model Claude Code is using for text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2a: Install the Claude Code plugin (recommended)
&lt;/h2&gt;

&lt;p&gt;The plugin is a Claude Code marketplace hosted on GitHub. Two commands install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add castaly-ai/modelflare-claude-code
claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;modelflare@modelflare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both commands print a confirmation (&lt;code&gt;âœ” Successfully added marketplace: modelflare&lt;/code&gt; and &lt;code&gt;âœ” Successfully installed plugin: modelflare@modelflare (scope: user)&lt;/code&gt;). The plugin carries the &lt;code&gt;modelflare-media&lt;/code&gt; skill, so after installing there is nothing else to configure besides &lt;code&gt;MODELFLARE_API_KEY&lt;/code&gt;. To pick up a newer version later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin update modelflare@modelflare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2b: Or install the Media Skill directly
&lt;/h2&gt;

&lt;p&gt;If you would rather not use the plugin system, or you also want the skill in Codex, the docs offer an install that works for any agent. The simplest version is to ask the agent itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install this skill: https://modelflare.dev/skills/modelflare-media/modelflare-media.zip (or use the attached zip), then read its SKILL.md. If you do not support skills, extract it anywhere and follow SKILL.md. My key is in the environment variable MODELFLARE_API_KEY.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or place the files yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://modelflare.dev/skills/modelflare-media/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://modelflare.dev/skills/modelflare-media/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both install into &lt;code&gt;~/.claude/skills/modelflare-media&lt;/code&gt;, which Claude Code reads as a user-level skill. A project-level copy goes into &lt;code&gt;.claude/skills/&lt;/code&gt; inside the repository. For Codex, set &lt;code&gt;SKILL_DIR&lt;/code&gt; first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SKILL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.codex/skills curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://modelflare.dev/skills/modelflare-media/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The helper script needs only Node.js 18 or newer. You can read the &lt;a href="https://modelflare.dev/skills/modelflare-media/SKILL.md" rel="noopener noreferrer"&gt;SKILL.md&lt;/a&gt; before installing to see exactly what the agent will follow.&lt;/p&gt;

&lt;p&gt;Install either the plugin or the standalone skill, not both, so Claude Code does not see two copies of the same skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2c: Or add the MCP server
&lt;/h2&gt;

&lt;p&gt;The MCP server exposes media generation as explicit tools. Its README gives this client configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"modelflare"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelflare/mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MODELFLARE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your Smart API Key&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Claude Code you can put that block in a project &lt;code&gt;.mcp.json&lt;/code&gt;, or register it from the terminal with Claude Code's own &lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;&lt;code&gt;claude mcp add&lt;/code&gt;&lt;/a&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio modelflare &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelflare/mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code confirms the registration and health-checks the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ claude mcp add --env MODELFLARE_API_KEY="$MODELFLARE_API_KEY" \
    --transport stdio modelflare -- npx -y @modelflare/mcp-server
Added stdio MCP server modelflare with command: npx -y @modelflare/mcp-server to local config

$ claude mcp list
Checking MCP server healthâ€¦

modelflare: npx -y @modelflare/mcp-server - âœ” Connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default this is a local-scope entry, private to you in the current project. Remove it with &lt;code&gt;claude mcp remove modelflare -s local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The npm package (current version 0.1.1) declares &lt;code&gt;node &amp;gt;=20.3&lt;/code&gt;. It reads four environment variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODELFLARE_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Smart API Key (required)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODELFLARE_BASE_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;global&lt;/code&gt; â†’ &lt;code&gt;https://modelflare.dev/v1&lt;/code&gt; (default), &lt;code&gt;china&lt;/code&gt; â†’ &lt;code&gt;https://origin.modelflare.dev/v1&lt;/code&gt;, or a custom URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODELFLARE_OUTPUT_DIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where images and videos are saved (default &lt;code&gt;./modelflare-output&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODELFLARE_LOCALE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;en&lt;/code&gt; (default) or &lt;code&gt;zh&lt;/code&gt; for error messages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Claude Desktop, the &lt;a href="https://github.com/castaly-ai/modelflare-mcp/releases" rel="noopener noreferrer"&gt;GitHub releases&lt;/a&gt; include a &lt;code&gt;modelflare-mcp-&amp;lt;version&amp;gt;.mcpb&lt;/code&gt; bundle that asks for the key, base URL and output folder on open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: a hero image and a looping header video
&lt;/h2&gt;

&lt;p&gt;Here is the workflow we use for a small product landing page, with Claude Code started in the project root and the plugin installed. Under it is a real run of the MCP tool behind prompt 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt 1:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate a 16:9 poster of a coastal observatory at dusk with gpt-image-2 and save it to public/hero.png.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the skill, Claude Code reads &lt;code&gt;SKILL.md&lt;/code&gt;, fetches the live catalog from &lt;code&gt;https://modelflare.dev/api/pricing&lt;/code&gt; to confirm the key can use &lt;code&gt;gpt-image-2&lt;/code&gt; and what it costs, opens the &lt;a href="https://docs.modelflare.dev/models/image/gpt-image-2/" rel="noopener noreferrer"&gt;gpt-image-2 model page&lt;/a&gt; for the exact request body and limits, and calls &lt;code&gt;POST /v1/images/generations&lt;/code&gt; with its own HTTP client. Every call carries the header &lt;code&gt;X-Modelflare-Skill: modelflare-media/1.0&lt;/code&gt;, so you can filter those requests in the &lt;a href="https://modelflare.dev/usage-logs" rel="noopener noreferrer"&gt;usage logs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the MCP server, the same sentence becomes a &lt;code&gt;generate_image&lt;/code&gt; tool call. Model-specific fields go in &lt;code&gt;parameters&lt;/code&gt; and are sent unchanged, and the result is saved immediately because result URLs expire. Here is a real &lt;code&gt;generate_image&lt;/code&gt; call from our test run, at the cheapest quality setting the &lt;a href="https://docs.modelflare.dev/models/image/gpt-image-2/" rel="noopener noreferrer"&gt;gpt-image-2 page&lt;/a&gt; documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tool: generate_image
arguments:
  model: gpt-image-2
  prompt: A 16:9 poster of a coastal observatory at dusk, flat illustration
  parameters: { "size": "1536x1024", "quality": "low" }

result:
  Saved 1 image(s):
  ./modelflare-output/gpt-image-2-2026-10-14T14-22-22-789Z.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It took about 35 seconds and produced a 1.9 MB PNG. &lt;code&gt;list_models&lt;/code&gt; on the same key returned image and video models, each with its docs URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-image-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.modelflare.dev/models/image/gpt-image-2/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seedance-2.0-fast"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.modelflare.dev/models/video/seedance-2-0-fast/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wan2.7-image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.modelflare.dev/models/image/wan2-7-image/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Trimmed to three entries; the full list follows your account's access.)&lt;/p&gt;

&lt;p&gt;If the upstream is briefly unavailable, the tool returns the error with a request ID rather than failing silently. On our first attempt we got exactly that, and the retry a minute later succeeded. Quote the request ID if you contact support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt 2:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn public/hero.png into a 5 second 1080p video with Seedance and save it as public/hero-loop.mp4.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Video is asynchronous. With the MCP server you can watch the three steps happen: &lt;code&gt;create_video&lt;/code&gt; starts the task and returns its ID, &lt;code&gt;get_video_status&lt;/code&gt; reports progress, and &lt;code&gt;download_video&lt;/code&gt; saves the finished file (it can wait up to 300 seconds first). With the skill, Claude Code performs the same create-poll-save cycle itself, using the contract from the model page, for example &lt;a href="https://docs.modelflare.dev/models/video/seedance-2-0/" rel="noopener noreferrer"&gt;seedance-2.0&lt;/a&gt; or &lt;a href="https://docs.modelflare.dev/models/video/seedance-2-0-fast/" rel="noopener noreferrer"&gt;seedance-2.0-fast&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The local start frame is embedded in the request as a data URL. The skill does not upload files to any storage, so keep reference images small (a few MB).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt 3, before committing to a model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which video models under 0.10 USD per second can do image-to-video? Show the price per 5-second clip.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the catalog is read live, the answer reflects today's prices and today's models, not what was true when the skill was installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Name the model when it matters.&lt;/strong&gt; "With gpt-image-2" or "with Seedance" pins the choice. Leave it out and the agent picks from what your key can reach, which is fine for drafts but makes results harder to reproduce. Use exact IDs from &lt;a href="https://modelflare.dev/pricing" rel="noopener noreferrer"&gt;Models &amp;amp; Pricing&lt;/a&gt;; the docs list 14 image models and 28 video models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask for a quote first on video.&lt;/strong&gt; Video is billed per second or per token depending on the model (see the &lt;a href="https://docs.modelflare.dev/models/video/" rel="noopener noreferrer"&gt;video models table&lt;/a&gt;). A one-line "quote this before running it" saves surprises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep output out of git by default.&lt;/strong&gt; The MCP server writes to &lt;code&gt;./modelflare-output&lt;/code&gt; unless you set &lt;code&gt;MODELFLARE_OUTPUT_DIR&lt;/code&gt;. Add that folder to &lt;code&gt;.gitignore&lt;/code&gt; and move only the assets you keep into &lt;code&gt;public/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproduce work from the browser.&lt;/strong&gt; Every generation in the &lt;a href="https://castaly.modelflare.dev/workbench" rel="noopener noreferrer"&gt;Castaly workbench&lt;/a&gt; has a "Reproduce this in your own agent" panel with the install message and a task brief. Paste the brief into Claude Code and it repeats the same model and parameters against your key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read errors literally.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt;: the key is missing or invalid. Create a Smart API Key at &lt;a href="https://modelflare.dev/keys" rel="noopener noreferrer"&gt;modelflare.dev/keys&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;model_not_found&lt;/code&gt; / &lt;code&gt;model_unavailable&lt;/code&gt;: a regular key only calls its own group's models. With a Smart API Key, the model is not available to your account right now. The skill cannot unlock model groups.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403 insufficient_user_quota&lt;/code&gt;: top up at &lt;a href="https://modelflare.dev/billing" rel="noopener noreferrer"&gt;modelflare.dev/billing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unsupported_endpoint&lt;/code&gt;: the request went to the wrong contract (for example, a Gemini image model sent to &lt;code&gt;/v1/images/generations&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Know what the skill does not do.&lt;/strong&gt; It does not decide retries, batching or what to report back; that is between you and your agent. It only documents the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Media Skill guide: &lt;a href="https://docs.modelflare.dev/guides/clients/media-skill/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/clients/media-skill&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Claude Code setup: &lt;a href="https://docs.modelflare.dev/guides/clients/claude-code/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/clients/claude-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Smart API Key: &lt;a href="https://docs.modelflare.dev/guides/features/smart-api-key/" rel="noopener noreferrer"&gt;docs.modelflare.dev/guides/features/smart-api-key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Image models: &lt;a href="https://docs.modelflare.dev/models/image/" rel="noopener noreferrer"&gt;docs.modelflare.dev/models/image&lt;/a&gt; Â· Video models: &lt;a href="https://docs.modelflare.dev/models/video/" rel="noopener noreferrer"&gt;docs.modelflare.dev/models/video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Claude Code plugin: &lt;a href="https://github.com/castaly-ai/modelflare-claude-code" rel="noopener noreferrer"&gt;github.com/castaly-ai/modelflare-claude-code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP server: &lt;a href="https://www.npmjs.com/package/@modelflare/mcp-server" rel="noopener noreferrer"&gt;npmjs.com/package/@modelflare/mcp-server&lt;/a&gt; Â· &lt;a href="https://github.com/castaly-ai/modelflare-mcp" rel="noopener noreferrer"&gt;github.com/castaly-ai/modelflare-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is not that Claude Code can now draw. It is that the asset step stops being a context switch: one key, one sentence, one file in the place your code expects it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>mcp</category>
      <category>imagegeneration</category>
    </item>
    <item>
      <title>Function Calling: Responses API vs Chat Completions</title>
      <dc:creator>Magnus</dc:creator>
      <pubDate>Tue, 13 Oct 2026 05:38:42 +0000</pubDate>
      <link>https://dev.to/modelflare_dev/function-calling-responses-api-vs-chat-completions-1djb</link>
      <guid>https://dev.to/modelflare_dev/function-calling-responses-api-vs-chat-completions-1djb</guid>
      <description>&lt;p&gt;A wire-level comparison of function definitions, Call IDs, result messages, streaming arguments, authorization, idempotency, and route compatibility.&lt;/p&gt;

&lt;p&gt;Responses and Chat Completions can both ask an application to run a function, but they represent the tool loop differently. Chat Completions puts tool definitions under &lt;code&gt;tools[].function&lt;/code&gt;, returns &lt;code&gt;message.tool_calls&lt;/code&gt;, and accepts results as &lt;code&gt;role: "tool"&lt;/code&gt; messages. Responses uses flat function definitions, returns typed &lt;code&gt;function_call&lt;/code&gt; output items, and accepts &lt;code&gt;function_call_output&lt;/code&gt; items linked by &lt;code&gt;call_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model does not execute your application function. Your code must validate the proposed arguments, authorize the operation, execute it exactly as intended, return a result to the model, and prevent duplicate side effects when a request is retried.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool loop has four steps
&lt;/h2&gt;

&lt;p&gt;The endpoint-specific JSON differs, but the application state machine is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Declare an allowed function and its argument schema
2. Receive one or more model-proposed function calls
3. Validate, authorize, and execute each call in application code
4. Return each result using the call's correlation ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after step four can the model produce an answer grounded in the tool result. If the model requests another tool, the loop repeats with a new call ID.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developers.openai.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI Function Calling guide&lt;/a&gt; describes this as a multi-step exchange. Treating the first tool call as a final answer is a common integration error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the wire contracts
&lt;/h2&gt;

&lt;p&gt;The main differences are structural rather than conceptual.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Responses API&lt;/th&gt;
&lt;th&gt;Chat Completions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Function definition&lt;/td&gt;
&lt;td&gt;Flat &lt;code&gt;tools[]&lt;/code&gt; item with &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;parameters&lt;/code&gt;, &lt;code&gt;strict&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tools[]&lt;/code&gt; item with those fields nested under &lt;code&gt;function&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model-proposed call&lt;/td&gt;
&lt;td&gt;Typed &lt;code&gt;function_call&lt;/code&gt; output item&lt;/td&gt;
&lt;td&gt;Assistant message &lt;code&gt;tool_calls[]&lt;/code&gt; entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correlation identifier&lt;/td&gt;
&lt;td&gt;&lt;code&gt;call_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tool call &lt;code&gt;id&lt;/code&gt;, returned as &lt;code&gt;tool_call_id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;function_call.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tool_calls[].function.name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arguments&lt;/td&gt;
&lt;td&gt;JSON string in &lt;code&gt;function_call.arguments&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;JSON string in &lt;code&gt;tool_calls[].function.arguments&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool result&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;function_call_output&lt;/code&gt; input item&lt;/td&gt;
&lt;td&gt;Message with &lt;code&gt;role: "tool"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common streamed arguments&lt;/td&gt;
&lt;td&gt;Typed function-argument delta events&lt;/td&gt;
&lt;td&gt;Indexed &lt;code&gt;delta.tool_calls[]&lt;/code&gt; fragments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final text&lt;/td&gt;
&lt;td&gt;Typed output items and output-text helpers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;choices[0].message.content&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not correlate calls by array position. Parallel calls and streamed chunks can arrive in an order that differs from the order in which your application finishes. The explicit call ID is the stable join key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define one strict function schema
&lt;/h2&gt;

&lt;p&gt;The following examples expose a read-only &lt;code&gt;get_delivery_status&lt;/code&gt; function. The model may request an order status, but it cannot change an order, issue a refund, or access arbitrary storage.&lt;/p&gt;

&lt;p&gt;The argument schema is deliberately narrow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^ORDER-[0-9]{4}$"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"order_id"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"additionalProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strict: true&lt;/code&gt; asks the supported model to adhere to the function argument schema. The application must still parse and validate the JSON before execution. Pattern support and other JSON Schema features can vary by model or provider, so the same compatibility gate used for &lt;a href="https://modelflare.dev/articles/structured-outputs-openai-compatible-api" rel="noopener noreferrer"&gt;Structured Outputs&lt;/a&gt; also applies to function arguments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement the loop with the Responses API
&lt;/h2&gt;

&lt;p&gt;This JavaScript example keeps the exchange stateless by returning the model’s output items with the function result. It uses a deterministic local function so the control flow is visible without introducing another external API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://modelflare.dev/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MODEL_ID is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ORDER-1001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in_transit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;eta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-08-06&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_delivery_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Return the current delivery status for one order.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^ORDER-[0-9]{4}$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_delivery_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Tool is not allowed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawArguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^ORDER-&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]{4}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid order_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Where is ORDER-1001?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;parallel_tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function_call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolOutputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function_call_output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;toolOutputs&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;parallel_tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a stateful integration, Responses also supports continuing from a prior response where the route and storage policy permit it. A stateless example makes the complete transcript explicit and avoids assuming that every OpenAI-compatible route stores response state.&lt;/p&gt;

&lt;p&gt;The critical fields are &lt;code&gt;call.call_id&lt;/code&gt; on the model output and the matching &lt;code&gt;call_id&lt;/code&gt; on &lt;code&gt;function_call_output&lt;/code&gt;. Returning a result with the wrong ID disconnects the data from the model’s request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement the loop with Chat Completions
&lt;/h2&gt;

&lt;p&gt;The same application function can be used with Chat Completions. The tool definition and result message use the Chat wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODELFLARE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://modelflare.dev/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MODEL_ID is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ORDER-1001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in_transit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;eta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-08-06&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_delivery_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Return the current delivery status for one order.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^ORDER-[0-9]{4}$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawArguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_delivery_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Tool is not allowed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawArguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^ORDER-&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]{4}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid order_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Where is ORDER-1001?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;parallel_tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assistant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tool_call_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;parallel_tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the first assistant message must be appended to &lt;code&gt;messages&lt;/code&gt; before its tool results. Each result uses &lt;code&gt;tool_call_id: call.id&lt;/code&gt;. Omitting the assistant tool-call message or returning an unmatched ID produces an invalid conversation history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream arguments as fragments, not complete JSON
&lt;/h2&gt;

&lt;p&gt;In streaming mode, function arguments can arrive in fragments. A chunk that contains &lt;code&gt;{"order&lt;/code&gt; is not invalid JSON; it is incomplete JSON.&lt;/p&gt;

&lt;p&gt;The safe parser keeps one buffer per call identity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;identify the call or indexed call slot from the event;&lt;/li&gt;
&lt;li&gt;append each argument delta to that call’s buffer;&lt;/li&gt;
&lt;li&gt;wait for the endpoint’s argument-done or completed signal;&lt;/li&gt;
&lt;li&gt;parse the complete string once;&lt;/li&gt;
&lt;li&gt;validate and execute only after completion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Responses uses typed events for function-call arguments and output items. Chat Completions emits fragments in &lt;code&gt;choices[].delta.tool_calls[]&lt;/code&gt;, where the index identifies the in-progress entry and the final tool call carries its stable ID. Do not execute a function when the first fragment arrives.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://modelflare.dev/articles/ai-api-streaming-guide" rel="noopener noreferrer"&gt;AI API Streaming Guide&lt;/a&gt; explains why connection, first event, first effective output, idle timeout, and completion should be measured separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make tool execution safe and idempotent
&lt;/h2&gt;

&lt;p&gt;Strict arguments improve structure, not authorization. Before executing any call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow only registered function names;&lt;/li&gt;
&lt;li&gt;parse arguments with a size limit;&lt;/li&gt;
&lt;li&gt;validate the complete schema in application code;&lt;/li&gt;
&lt;li&gt;authorize the current user or workload for the requested resource;&lt;/li&gt;
&lt;li&gt;separate read-only tools from tools with side effects;&lt;/li&gt;
&lt;li&gt;redact secrets and sensitive output before returning it to the model;&lt;/li&gt;
&lt;li&gt;use a deadline and bounded downstream retries;&lt;/li&gt;
&lt;li&gt;record a safe operation reference, not raw secrets or private content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a side-effecting tool, use an idempotency key derived from a stable application request and call identity. Store the completed result before returning it to the model. If a network retry replays the same call, return the stored result rather than issuing a second refund, message, deployment, or database mutation.&lt;/p&gt;

&lt;p&gt;The model-generated function name and arguments are untrusted input. A valid schema cannot grant permission, prove ownership, or replace a transaction boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide how to handle multiple calls
&lt;/h2&gt;

&lt;p&gt;The examples set &lt;code&gt;parallel_tool_calls: false&lt;/code&gt; to keep the state machine easy to inspect. If parallel calls are enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;correlate every result by call ID, never by completion order;&lt;/li&gt;
&lt;li&gt;enforce a maximum number of calls per response;&lt;/li&gt;
&lt;li&gt;apply a concurrency limit in the application;&lt;/li&gt;
&lt;li&gt;define whether one failed tool cancels, blocks, or coexists with successful tools;&lt;/li&gt;
&lt;li&gt;return one result for every accepted call;&lt;/li&gt;
&lt;li&gt;keep side-effecting operations serialized when their order matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parallel execution can reduce latency for independent reads, but it increases authorization, ordering, retry, and partial-failure complexity. Enable it because the workload benefits, not because the parameter exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the Modelflare compatibility boundary
&lt;/h2&gt;

&lt;p&gt;Modelflare’s OpenAI compatibility code preserves strict function definitions and maps the supported function-call shapes between Responses and Chat Completions on eligible OpenAI/Codex routes. That mapping is deliberately narrower than the complete Responses tool surface.&lt;/p&gt;

&lt;p&gt;Application-defined function tools can be represented in both formats. Responses-only hosted tools—such as provider-run search, code execution, or other built-in tool types—are not equivalent to application function calling and cannot be assumed to convert into Chat Completions.&lt;/p&gt;

&lt;p&gt;Other OpenAI-compatible model families are exposed through raw Chat Completions pass-through unless separately verified. For those routes, the upstream provider decides whether &lt;code&gt;tools&lt;/code&gt;, &lt;code&gt;strict&lt;/code&gt;, parallel calls, streaming argument fragments, and specific &lt;code&gt;tool_choice&lt;/code&gt; shapes are supported.&lt;/p&gt;

&lt;p&gt;Before enabling a route, run the complete loop rather than checking only whether the first request returns &lt;code&gt;200&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Required evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One read-only call&lt;/td&gt;
&lt;td&gt;Function name, parsed arguments, call ID, result linkage, final text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid arguments&lt;/td&gt;
&lt;td&gt;Explicit validation failure with no tool execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown function&lt;/td&gt;
&lt;td&gt;Rejected by the application allowlist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No tool required&lt;/td&gt;
&lt;td&gt;Normal final text without a fabricated call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming call&lt;/td&gt;
&lt;td&gt;Complete reconstructed arguments and correct call ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeated request&lt;/td&gt;
&lt;td&gt;One side effect or one cached result, not duplicate execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple calls&lt;/td&gt;
&lt;td&gt;Correct correlation independent of completion order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route fallback&lt;/td&gt;
&lt;td&gt;Same model, protocol, tool schema, and result contract preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use &lt;a href="https://modelflare.dev/articles/reliable-ai-api-routing" rel="noopener noreferrer"&gt;Reliable AI API Routing&lt;/a&gt; to keep route changes explicit, and &lt;a href="https://modelflare.dev/articles/api-key-security-cost-controls" rel="noopener noreferrer"&gt;AI API Key Security&lt;/a&gt; to separate workloads and permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the format by the application contract
&lt;/h2&gt;

&lt;p&gt;Use Responses when the application benefits from typed output items, the Responses event model, state-continuation options, or other verified Responses capabilities. Use Chat Completions when the application already owns a stable message transcript and the selected provider’s tool contract is verified. The broader endpoint decision is covered in &lt;a href="https://modelflare.dev/articles/responses-api-vs-chat-completions" rel="noopener noreferrer"&gt;Responses API vs Chat Completions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The implementation standard should be the same in both cases: explicit tool allowlists, strict argument schemas, application-side validation, authorization before execution, correlation by call ID, idempotency for side effects, and a complete result returned to the model. Those controls—not the endpoint name—make a function-calling workflow reliable.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://modelflare.dev/articles/function-calling-responses-vs-chat-completions?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog" rel="noopener noreferrer"&gt;modelflare.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm part of the Modelflare team, which builds the AI API gateway mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>llm</category>
      <category>api</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
