<?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: Stainless</title>
    <description>The latest articles on DEV Community by Stainless (@stainlessapi).</description>
    <link>https://dev.to/stainlessapi</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12377%2F48e1d70c-a8f8-4292-bed4-b8c6e27b7fdc.png</url>
      <title>DEV Community: Stainless</title>
      <link>https://dev.to/stainlessapi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stainlessapi"/>
    <language>en</language>
    <item>
      <title>Introducing the (experimental!) Stainless SQL SDK generator</title>
      <dc:creator>CJ Avilla</dc:creator>
      <pubDate>Wed, 18 Feb 2026 15:09:31 +0000</pubDate>
      <link>https://dev.to/stainlessapi/introducing-the-experimental-stainless-sql-sdk-generator-2bin</link>
      <guid>https://dev.to/stainlessapi/introducing-the-experimental-stainless-sql-sdk-generator-2bin</guid>
      <description>&lt;p&gt;If you run analytics, reconciliation, or batch jobs in PostgreSQL, pulling data from REST APIs usually means ETL scripts, intermediary services, or brittle sync pipelines. The data you need is one HTTP request away, but PostgreSQL is not designed to call HTTP endpoints as part of a query plan.&lt;/p&gt;

&lt;p&gt;You can bolt on ad-hoc HTTP functions, but you lose typing, pagination ergonomics, and a spec-driven interface.&lt;/p&gt;

&lt;p&gt;We built an experimental SQL SDK generator that turns an OpenAPI spec into a &lt;a href="https://www.postgresql.org/docs/current/sql-createextension.html" rel="noopener noreferrer"&gt;PostgreSQL extension&lt;/a&gt; that uses &lt;a href="https://www.postgresql.org/docs/current/plpython.html" rel="noopener noreferrer"&gt;PL/Python&lt;/a&gt; for HTTP requests. Every API endpoint becomes a SQL function. Every response type becomes a &lt;a href="https://www.postgresql.org/docs/current/rowtypes.html" rel="noopener noreferrer"&gt;composite type&lt;/a&gt;. (We explain why we chose SQL functions over Foreign Data Wrappers below.)&lt;/p&gt;

&lt;p&gt;A SQL SDK enables users to query your API using standard SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get: REST API calls as SQL functions
&lt;/h2&gt;

&lt;p&gt;To show you how our SQL SDK generator works, we used a subset of the &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; OpenAPI spec to generate &lt;a href="https://github.com/stainless-commons/stripe-sql" rel="noopener noreferrer"&gt;an experimental SQL SDK&lt;/a&gt;. You can install it by cloning &lt;a href="https://github.com/stainless-commons/stripe-sql" rel="noopener noreferrer"&gt;the extension repo&lt;/a&gt; and running &lt;code&gt;./scripts/repl&lt;/code&gt; or by manually building with &lt;code&gt;make install&lt;/code&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jhj5uise89t9mbrjz5g.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jhj5uise89t9mbrjz5g.gif" alt="Demo showing how to query with SQL DSK functions" width="560" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each API endpoint maps to a SQL function. Each resource maps to a PostgreSQL schema. The result is a typed interface to a REST API, accessible from any PostgreSQL client.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The extension is built on two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PL/Python functions&lt;/strong&gt; call a Stainless-generated Python SDK to handle HTTP requests, authentication, retries, request serialization, response parsing, and other networking concerns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL function wrappers&lt;/strong&gt; initialize the client, perform lenient type adaptation from Python objects to PostgreSQL composite types, and expose a typed SQL interface. A single client is initialized per session for performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paginated endpoints employ SQL functions that use &lt;code&gt;WITH RECURSIVE&lt;/code&gt; CTEs to repeatedly fetch pages using PL/Python function calls. Because the outer SQL function can be used &lt;a href="https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions" rel="noopener noreferrer"&gt;inline&lt;/a&gt;, PostgreSQL can push down &lt;code&gt;LIMIT&lt;/code&gt; clauses and stop requesting new pages once enough rows have been produced.&lt;/p&gt;

&lt;p&gt;If pagination were implemented entirely in Python, PostgreSQL would buffer the full result set at the PL/Python boundary before applying &lt;code&gt;LIMIT&lt;/code&gt;. By keeping auto-pagination in SQL, the query planner retains control over row consumption and can short-circuit additional HTTP requests.&lt;/p&gt;

&lt;p&gt;The result is a standard PostgreSQL extension. Install with &lt;code&gt;make install&lt;/code&gt; and load with &lt;code&gt;CREATE EXTENSION&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;plpython3u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure with &lt;code&gt;ALTER DATABASE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, to configure an API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- For persistent configuration (ensure your logs are secured):&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;my_database&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sk_test_...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Or for a single session:&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sk_test_...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cnbaaomf9t6nuk2hdt1.jpeg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cnbaaomf9t6nuk2hdt1.jpeg" alt="Two layers of the Stainless SQL SDK from the SQL interface down into the HTTP layer and API response back and then back with typed composite rows" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Type-safe composite types, not &lt;code&gt;JSONB&lt;/code&gt; blobs
&lt;/h2&gt;

&lt;p&gt;Each API resource maps to a PostgreSQL schema. &lt;code&gt;stripe_customer&lt;/code&gt; contains the &lt;code&gt;customer&lt;/code&gt; composite type with typed fields: &lt;code&gt;id TEXT&lt;/code&gt;, &lt;code&gt;created BIGINT&lt;/code&gt;, &lt;code&gt;email TEXT&lt;/code&gt;, &lt;code&gt;livemode BOOLEAN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generator types fields where the spec is precise. Nested objects map to composite types, and arrays become typed arrays.&lt;/p&gt;

&lt;p&gt;You get real column access instead of &lt;code&gt;JSONB-&amp;gt;&amp;gt;'field'&lt;/code&gt; casting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Typed fields, not JSONB extraction&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;livemode&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For complex parameters, the extension generates &lt;code&gt;make_*&lt;/code&gt; constructor functions. The generator produces these from the OpenAPI spec, and they stay in sync when the spec changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;Create a customer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'jane@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Jane Doe'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List customers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension handles pagination automatically. A &lt;code&gt;LIMIT 200&lt;/code&gt; for an API that returns 100 items per page makes exactly two HTTP requests. Apply &lt;code&gt;LIMIT&lt;/code&gt; as close to the API function call as possible so PostgreSQL can stop consuming rows once the limit is satisfied.&lt;/p&gt;

&lt;p&gt;The real payoff is combining API data with your existing tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;stripe_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query joins live Stripe customer data from the API with your local &lt;code&gt;orders&lt;/code&gt; table. This replaces an ETL pipeline and staging tables with a single query.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p98vp952s5snve4g8b6.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p98vp952s5snve4g8b6.png" alt="Stainless SQL SDK generator vs traditional" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SQL SDKs fit
&lt;/h2&gt;

&lt;p&gt;SQL SDKs work well in many workflows, but they’re not a universal solution for every problem. They are most effective when you want to pull API data into analytical queries, scheduled jobs, or batch pipelines that already run inside PostgreSQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  ETL and data sync
&lt;/h3&gt;

&lt;p&gt;Combine materialized views with &lt;code&gt;pg_cron&lt;/code&gt; for scheduled data pulls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;stripe_customers&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;stripe_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;        
  &lt;span class="n"&gt;job_name&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'refresh-stripe-customers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schedule&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0 */4 * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;command&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'REFRESH MATERIALIZED VIEW CONCURRENTLY stripe_customers'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every four hours, your database has a fresh snapshot of Stripe customers. Query it like any other table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business analytics
&lt;/h3&gt;

&lt;p&gt;Ad-hoc queries that combine API data with your operational database: revenue reconciliation, customer segmentation, churn analysis. The queries run where the data already lives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch LLM workflows
&lt;/h3&gt;

&lt;p&gt;Select the latest support tickets, call an LLM classification endpoint, and write labels back to a table for downstream reporting. As LLM APIs publish OpenAPI specs, SQL SDKs make batch inference from SQL practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where SQL SDKs are not a fit
&lt;/h3&gt;

&lt;p&gt;SQL SDKs are not intended for low latency production OLTP paths. Each function call issues one or more HTTP requests, introducing network latency, rate limits, and remote failure modes. PostgreSQL’s planner optimizes relational operators over local data; it is not a cost based planner for REST APIs. Core SQL concepts such as predicate pushdown, join reordering, and index selectivity do not map cleanly to HTTP endpoints, so small query changes can result in additional remote calls. Use SQL SDKs for analytical and batch workloads where expressiveness and integration matter more than tail latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not generate Foreign Data Wrappers?
&lt;/h2&gt;

&lt;p&gt;Generating &lt;a href="https://wiki.postgresql.org/wiki/Foreign_data_wrappers" rel="noopener noreferrer"&gt;Foreign Data Wrappers&lt;/a&gt; (FDWs) for APIs is appealing, but there are several drawbacks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance is hard to reason about&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A REST API FDW would hide the HTTP requests it makes.&lt;/p&gt;

&lt;p&gt;The FDW has to determine which requests to make based on a query, making it a query planner for HTTP requests. A small change to a query could silently result in a less performant plan with many more HTTP requests. This would not be easy to debug.&lt;/p&gt;

&lt;p&gt;An FDW over a REST API ends up being a leaky abstraction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;APIs are not always relational&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FDWs shine when the data they’re modeling is relational. If the data an API provides is not relational, then it’s not well-suited for being represented as tables in an FDW.&lt;/p&gt;

&lt;p&gt;Plain SQL functions work for any API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A new interface to learn&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modeling API data in FDW tables would result in a different interface from the API and its SDKs, which means your users would have to learn a new API.&lt;/p&gt;

&lt;p&gt;Plain SQL functions grouped by resource matches the existing structure of your API and its SDKs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current status: experimental
&lt;/h2&gt;

&lt;p&gt;The SQL SDK generator works best with OpenAPI 3.x specifications that use standard request and response bodies and predictable pagination patterns. We generated a Stripe SQL SDK as the first example, but the generator is not Stripe-specific.&lt;/p&gt;

&lt;p&gt;This is an experimental release. We're sharing it because we want feedback from developers who work at the intersection of APIs and databases. We're also expanding coverage and want to hear about OpenAPI specs that break the generator.&lt;/p&gt;

&lt;p&gt;Expect breaking changes. This is not ready for production workloads. We are actively developing the generator and want to hear which use cases matter most to you.&lt;/p&gt;

&lt;p&gt;If you’re using our SQL SDK generator in any capacity we’d love to hear from you. &lt;a href="mailto:support@stainless.com"&gt;Let us know&lt;/a&gt; what you’re working on, if you run into any issues, or if you have feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;The Stripe SQL SDK is available as a reference for what the generator produces. Clone the &lt;a href="https://github.com/stainless-commons/stripe-sql" rel="noopener noreferrer"&gt;repo&lt;/a&gt;, run &lt;code&gt;make install&lt;/code&gt;, load with &lt;code&gt;CREATE EXTENSION stripe&lt;/code&gt;, and set &lt;code&gt;stripe.secret_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Want a generated extension for your API? &lt;a href="https://qm8il.share.hsforms.com/2Vcw2JNtBToOZeR2CJsc40g" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt; to get early access and tell us which APIs you want to query from SQL. We are working toward supporting any API with an OpenAPI spec.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>webdev</category>
      <category>openai</category>
      <category>database</category>
    </item>
    <item>
      <title>Sharper than ever: the Stainless C# SDK generator is now generally available</title>
      <dc:creator>CJ Avilla</dc:creator>
      <pubDate>Tue, 17 Feb 2026 16:48:00 +0000</pubDate>
      <link>https://dev.to/stainlessapi/sharper-than-ever-the-stainless-c-sdk-generator-is-now-generally-available-19kl</link>
      <guid>https://dev.to/stainlessapi/sharper-than-ever-the-stainless-c-sdk-generator-is-now-generally-available-19kl</guid>
      <description>&lt;p&gt;C# has a unique place in the programming language ecosystem. It powers enterprise software, AAA games, and even front-end web development through &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our goal is to generate idiomatic SDKs that work great in all of these environments. Stainless provides high-quality C# code that takes full advantage of the language, and it feels like our SDKs are hand-crafted by an experienced .NET developer. We include things like strong typing, composition with LINQ, and good tooling support. These are things every C# developer expects.&lt;/p&gt;

&lt;p&gt;It's a high bar, but it's one we managed to hit with the GA release of our C# SDK generator. Since launching our C# beta last summer, we've added more features while improving stability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Full .NET Standard 2.0 support&lt;/li&gt;
&lt;li&gt;Idiomatic types, conversions, and great editor tooling&lt;/li&gt;
&lt;li&gt;Async enumerables for SSE/JSONL and auto-paging&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.stainless.com/docs/guides/configure-strong-naming/" rel="noopener noreferrer"&gt;Strong naming support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Minimal dependencies&lt;/li&gt;
&lt;li&gt;Publishing to NuGet&lt;/li&gt;
&lt;li&gt;File uploads and downloads&lt;/li&gt;
&lt;li&gt;Sensible equality methods&lt;/li&gt;
&lt;li&gt;Built-in generated tests&lt;/li&gt;
&lt;li&gt;The ability to make raw requests when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some noteworthy C# generator design decisions
&lt;/h2&gt;

&lt;p&gt;Our goal is to generate idiomatic SDKs that feel at home in each language's ecosystem. We also strive to support advanced features and functionality, even if a particular language doesn't have native support for them. This approach shines through in some of the design decisions we made.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;.NET Standard 2.0 support&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The .NET ecosystem is splintered. Lots of people use .NET Standard, which has a limited subset of the full .NET functionality common across various .NET implementations, such as on vanilla Windows, &lt;a href="https://learn.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide" rel="noopener noreferrer"&gt;UWP&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/products/functions" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; 1.x, &lt;a href="https://dotnet.microsoft.com/en-us/apps/xamarin" rel="noopener noreferrer"&gt;Xamarin&lt;/a&gt;, and many other runtimes. Others use .NET itself and have access to all of the modern features that make C# great. &lt;/p&gt;

&lt;p&gt;The SDKs we generate have wide compatibility and provide the best developer experience possible.  On .NET Standard 2.0, our SDKs are still full-featured, and have good DX even though some language features are missing.&lt;/p&gt;

&lt;p&gt;On .NET 8 and above, our SDKs are fully idiomatic to the language, and take advantage of the available features.&lt;/p&gt;

&lt;p&gt;We accomplished this by considering both stacks carefully as we built our generator. In many cases we were able to accomplish our goals with extensions which allowed us to create record classes that operate as normal classes on .NET Standard. In some cases we rely on preprocessor directives, although this is quite rare, and we ensure we only do this when truly required. As a result, our generated SDKs are still easy to read while supporting both stacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazing unions
&lt;/h3&gt;

&lt;p&gt;Like many languages, C# does not support unions or sum types natively. There are a lot of tradeoffs when designing unions, and we don't have time to get into them all here. The bottom line is that every language has a unique set of constraints, so careful consideration is needed.&lt;/p&gt;

&lt;p&gt;Our solution is for our unions to be backed internally by an &lt;code&gt;object&lt;/code&gt; and a &lt;code&gt;JsonElement&lt;/code&gt;. The &lt;code&gt;object&lt;/code&gt; holds the union variant if one exists, and the &lt;code&gt;JsonElement&lt;/code&gt; holds the raw JSON data from the API. This allows us to represent data that doesn’t match the union perfectly, which can be a problem if the OpenAPI schema deviates from the actual API. We have constructors for each possible variant, as well as an unknown variant constructor, which operates as an escape hatch. This can be useful for bypassing the OpenAPI schema when required.&lt;/p&gt;

&lt;p&gt;To complement the constructors, we create implicit convertors from the variants. This keeps the DX light, as users don't need to wrap all of their unions. We also generate helper functions for switching and matching, which are inspired by the popular &lt;a href="https://github.com/mcintyre321/OneOf" rel="noopener noreferrer"&gt;OneOf .NET library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, the goal was to make unions easy to use without compromising on type-safety or readability. Let’s take a look at an example below, where we have a Dealership SDK, which contains a &lt;code&gt;Vehicle&lt;/code&gt; union with &lt;code&gt;Car&lt;/code&gt; and &lt;code&gt;Truck&lt;/code&gt; variants.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VehicleConverter&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Vehicle&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&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="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Gets the raw Json value, if the user needs it&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_element&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt;
                &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&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="c1"&gt;// This property exists on both `Car` and `Truck`, so we&lt;/span&gt;
    &lt;span class="c1"&gt;// automatically bubble it up. We want the user to be able&lt;/span&gt;
    &lt;span class="c1"&gt;// to access it without having to destructure the union&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;WheelsCount&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WheelsCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;truck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WheelsCount&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="c1"&gt;// For constructing a car variant&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// For constructing a truck variant&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Truck&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// For constructing an unknown variant&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonElement&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_element&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Helper method for picking a specific variant&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;TryPickCar&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;NotNullWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;TryPickTruck&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;NotNullWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Truck&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Truck&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Exhaustive matching with no return type&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Truck&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;truck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Car&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Truck&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;truck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;default&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="nf"&gt;DealershipSdkInvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Data did not match any variant of Vehicle"&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="c1"&gt;// Exhaustive matching with return type&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;Match&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;car&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Truck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;truck&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Car&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Truck&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;truck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;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="nf"&gt;DealershipSdkInvalidDataException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Data did not match any variant of Vehicle"&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="c1"&gt;// These implicit operators are a lighter-weight way&lt;/span&gt;
    &lt;span class="c1"&gt;// to construct the union. Notice that we don't create one&lt;/span&gt;
    &lt;span class="c1"&gt;// for the unknown variant, as that's considered a more advanced&lt;/span&gt;
    &lt;span class="c1"&gt;// feature and in that case the user should be more explicit&lt;/span&gt;
    &lt;span class="c1"&gt;// about what they want.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="nf"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Car&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;implicit&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt; &lt;span class="nf"&gt;Vehicle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Truck&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ...snip...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design delivers strong developer experience without sacrificing type safety or readability, and still allows developers to bypass the OpenAPI schema when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutability by default
&lt;/h3&gt;

&lt;p&gt;Many C# SDKs do not use immutable record classes even though mutability leaves room for logic bugs, thread-safety issues, and potentially sub-optimal performance. Part of making a great SDK is preventing these foot-guns wherever possible, so that developers can focus on building software.&lt;/p&gt;

&lt;p&gt;Our C# SDKs use immutable record classes which are idiomatic and work around the issues above.  Models are record classes, arrays use &lt;code&gt;ImmutableArray&lt;/code&gt;, and dictionaries use &lt;code&gt;FrozenDictionary&lt;/code&gt;. While looking at existing C# SDKs, we found a lot of prior art of people using mutable classes, like &lt;code&gt;List&lt;/code&gt; and &lt;code&gt;Dictionary&lt;/code&gt;, inside of supposedly immutable classes. We believe in a higher bar than this and made sure the data structures we generate are truly immutable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type-safe models with flexibility
&lt;/h3&gt;

&lt;p&gt;A good SDK should make it difficult, but not impossible, to deviate from the OpenAPI spec. Usually, the spec is correct, and we want it to inform the type system and, by extension, the user. Pragmatically, sometimes the API deviates from its spec. Although there are numerous ways to work around this in the &lt;a href="https://www.stainless.com/docs#the-stainless-config" rel="noopener noreferrer"&gt;Stainless config&lt;/a&gt;, sometimes users still need to be able to use the SDK before this happens. This means our data model needs to support some amount of flexibility.&lt;/p&gt;

&lt;p&gt;We work around this by backing our models with an internal read-only dictionary of &lt;code&gt;&amp;lt;string, JsonElement&amp;gt;&lt;/code&gt; which holds the raw data. We immutably expose this to the user so that they can get the raw data if needed. Users can also construct models from their own dictionary, which allows them to bypass the schema when sending requests.&lt;/p&gt;

&lt;p&gt;When the user doesn’t need to bypass the schema, they can use the C# properties we generate to access the model’s data. This makes the experience seamless, as if those properties were the actual backing data themselves.&lt;/p&gt;

&lt;p&gt;Internally, we cache data so that the user doesn’t have to pay the JSON deserialization cost every time they access the same property. We do this in a thread-safe manner so that the end-user doesn’t have to think about it, and our models still appear entirely immutable.&lt;/p&gt;

&lt;p&gt;Here’s what a model might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonModelConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CatFromRaw&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;))]&lt;/span&gt;
&lt;span class="c1"&gt;// JsonModel holds RawData, which is the backing dictionary. It is&lt;/span&gt;
&lt;span class="c1"&gt;// also responsible for caching&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;class&lt;/span&gt; &lt;span class="n"&gt;Cat&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JsonModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Marked as required in the schema&lt;/span&gt;
    &lt;span class="c1"&gt;// (throws exception if missing)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FurColour&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetNotNullClass&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"furColour"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"furColour"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&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="c1"&gt;// Marked as optional in the schema&lt;/span&gt;
    &lt;span class="c1"&gt;// (returns null if missing)&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;WhiskerCount&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetNullableStruct&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"whiskerCount"&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"whiskerCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&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="c1"&gt;// Constructor for object initializer syntax&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cat&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="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cat&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat&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="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rawData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Constructor for bypassing the schema&lt;/span&gt;
&lt;span class="cp"&gt;#pragma warning disable CS8618
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SetsRequiredMembers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FrozenDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rawData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#pragma warning restore CS8618
&lt;/span&gt;
    &lt;span class="c1"&gt;// Constructor that allows setting all required members as&lt;/span&gt;
    &lt;span class="c1"&gt;// parameters, with the option of also using the&lt;/span&gt;
    &lt;span class="c1"&gt;// object initializer syntax&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SetsRequiredMembers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;furColour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FurColour&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;furColour&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... snip ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You would instantiate the object like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Cat&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FurColour&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"orange"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WhiskerCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Cat&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orange"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WhiskerCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trusted by teams in production
&lt;/h2&gt;

&lt;p&gt;The strongest proof of our SDK quality is real teams shipping production systems that depend on them. Here are a few companies using Stainless-generated C# SDKs today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/anthropics/anthropic-sdk-csharp" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trycourier/courier-csharp" rel="noopener noreferrer"&gt;Courier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/orbcorp/orb-csharp" rel="noopener noreferrer"&gt;Orb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dodopayments/dodopayments-csharp" rel="noopener noreferrer"&gt;Dodo Payments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ArcadeAI/arcade-dotnet" rel="noopener noreferrer"&gt;Arcade AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/browserbase/stagehand-net" rel="noopener noreferrer"&gt;Browserbase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our goal for this release was to make C# a first-class citizen in the Stainless ecosystem, with SDKs that feel at home in real .NET codebases. Our C# generator reflects that philosophy throughout: runtime-aware code generation, idiomatic models, strong typing with pragmatic escape hatches, and a focus on long-term maintainability over short-term convenience.&lt;/p&gt;

&lt;p&gt;If you’d like to dig deeper, explore the &lt;a href="https://www.stainless.com/changelog/c-sdk-generator-is-generally-available" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; or start generating SDKs today by signing up for a Stainless account.&lt;/p&gt;

&lt;p&gt;We’re excited to see what you build, and we’d love &lt;a href="mailto:support@stainless.com"&gt;feedback&lt;/a&gt; from teams adding C# to their supported languages.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>stainless</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Iterate on your SDKs locally with the Stainless Language Server</title>
      <dc:creator>CJ Avilla</dc:creator>
      <pubDate>Thu, 12 Feb 2026 20:10:18 +0000</pubDate>
      <link>https://dev.to/stainlessapi/iterate-on-your-sdks-locally-with-the-stainless-language-server-10m0</link>
      <guid>https://dev.to/stainlessapi/iterate-on-your-sdks-locally-with-the-stainless-language-server-10m0</guid>
      <description>&lt;p&gt;Stainless users configure their SDKs with two files: a &lt;code&gt;stainless.yml&lt;/code&gt; config and an OpenAPI spec. Together, these files define how your API maps to generated SDKs, from resource structure and method naming to type definitions and pagination behavior. Getting them right is what makes the difference between a generated SDK that feels hand-written and one that feels auto-generated.&lt;/p&gt;

&lt;p&gt;Until now, editing these files meant working in the Stainless Studio or pushing changes and waiting for build feedback. That workflow works, but there are some limitations. You make a change, trigger a build, wait for diagnostics, fix an issue, and repeat. For quick iterations on your config or spec, that loop is slower than it needs to be.&lt;/p&gt;

&lt;p&gt;Today, we're releasing the &lt;a href="https://www.stainless.com/docs/guides/iterate-with-lsp/" rel="noopener noreferrer"&gt;Stainless Language Server&lt;/a&gt;, which brings the editing capabilities of the Studio directly into your local development environment. If you use VS Code, Cursor, Neovim, Zed, or any editor that supports the Language Server Protocol, you can now get real-time feedback on your Stainless config and OpenAPI spec as you type.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Language Server does
&lt;/h2&gt;

&lt;p&gt;The Stainless Language Server understands the relationship between your &lt;code&gt;stainless.yml&lt;/code&gt; and your OpenAPI spec. It knows which endpoints map to which resources, which schemas are referenced where, and what valid configuration options look like in context. That understanding powers a set of features that make local editing significantly more productive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-time diagnostics and quick fixes
&lt;/h3&gt;

&lt;p&gt;The language server analyzes your Stainless config and OpenAPI spec in real-time, surfacing errors, warnings, and suggestions directly in your editor. This helps you catch issues before running a build. When a diagnostic does appear, common issues come with an autofix you can apply with one click, and warning-level diagnostics can be suppressed with a single action.&lt;/p&gt;

&lt;p&gt;It’s powered by the same diagnostic engine as Studio, so you get identical feedback locally without leaving your editor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intelligent completions
&lt;/h3&gt;

&lt;p&gt;When editing your &lt;code&gt;stainless.yml&lt;/code&gt;, the language server offers context-aware autocompletion. It suggests valid config keys, endpoint paths, and schema names drawn from your OpenAPI spec. If you're defining a new resource method, it will suggest available endpoints. If you're referencing a schema, it will complete from the schemas defined in your spec.&lt;/p&gt;

&lt;p&gt;This is especially useful for large APIs. Instead of switching between your config and a long OpenAPI spec to find the right path or schema name, completions surface the options directly.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhavrskeny7e3i03z31os.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhavrskeny7e3i03z31os.gif" alt="Stainless LSP Autocompletions" width="760" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Go to definition and find all references
&lt;/h3&gt;

&lt;p&gt;You can use "Go to definition" to jump from a reference in your Stainless config straight to the corresponding schema, parameter, or endpoint in your OpenAPI spec. Use "Find all references" to see where a particular schema or endpoint is used across both files.&lt;/p&gt;

&lt;p&gt;For teams working with specs that have hundreds of endpoints, this navigation alone saves significant time. You don't need to manually search through a large YAML file to find where a schema is defined or where an endpoint is referenced.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c20kwvdqbkzy3ml5ixt.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c20kwvdqbkzy3ml5ixt.gif" alt="Stainless LSP go-to-definition" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Codelenses in your OpenAPI spec
&lt;/h3&gt;

&lt;p&gt;Codelenses appear inline in your OpenAPI spec, showing which schemas and endpoints are already configured in your Stainless config. This gives you a quick visual overview of your API's SDK coverage without switching files. You can see at a glance which parts of your spec are represented in your SDKs and which aren't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live transformations
&lt;/h3&gt;

&lt;p&gt;If you use &lt;a href="https://www.stainless.com/blog/stainless-transforms-the-fast-lane-for-fixing-imperfect-openapi-specs" rel="noopener noreferrer"&gt;Stainless Transforms&lt;/a&gt; to reshape your OpenAPI spec, the language server generates and updates your transformed spec in real-time as you edit your config. You can see the effect of each transform immediately, without running a separate build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting set up
&lt;/h2&gt;

&lt;p&gt;Setup takes a few minutes. Install the Stainless CLI, initialize your workspace, and add the extension for your editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the CLI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap stainless-api/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;stl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialize your workspace:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stl init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;.stainless/workspace.json&lt;/code&gt; file that the language server uses to identify your project and locate your config and spec files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install the editor extension:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For VS Code and Cursor, search for "Stainless" in the Extensions view and install it. The extension bundles the langauge server, so no separate LSP installation is needed.&lt;/p&gt;

&lt;p&gt;For Neovim and other LSP-compatible editors, install the language server package globally via npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @stainless-api/stainless-language-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure your LSP client to run &lt;code&gt;stainless-language-server --stdio&lt;/code&gt;. The server activates for YAML and JSON files when it detects &lt;code&gt;.stainless/workspace.json&lt;/code&gt; in your project. The &lt;a href="https://www.stainless.com/docs/guides/iterate-with-lsp/#-install-the-extension-for-your-editor" rel="noopener noreferrer"&gt;setup guide&lt;/a&gt; has editor-specific configuration examples.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1wcvfjh7ug9bp6y5zjl.gif" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1wcvfjh7ug9bp6y5zjl.gif" alt="Stainless local dev setup" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The local development workflow
&lt;/h2&gt;

&lt;p&gt;With the language server running, the workflow for iterating on your SDK configuration looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your &lt;code&gt;stainless.yml&lt;/code&gt; and OpenAPI spec in your editor&lt;/li&gt;
&lt;li&gt;Edit your config with completions and inline validation guiding you&lt;/li&gt;
&lt;li&gt;See diagnostics in real-time as you type, catching misconfigurations before they reach a build&lt;/li&gt;
&lt;li&gt;Use go-to-definition to navigate between your config and spec&lt;/li&gt;
&lt;li&gt;Review codelenses to check SDK coverage across your API&lt;/li&gt;
&lt;li&gt;When you're satisfied, push your changes to Stainless for a full build&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same set of features available in the Stainless Studio, now running locally. For teams that prefer working in their own editor, or that want to use AI coding tools like Claude or Cursor alongside their Stainless config, the language server makes that possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;The Stainless Language Server is available now. Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=stainless.vscode-stainless" rel="noopener noreferrer"&gt;VS Code extension&lt;/a&gt; or the &lt;a href="https://www.npmjs.com/package/@stainless-api/stainless-language-server" rel="noopener noreferrer"&gt;npm package&lt;/a&gt; and check out the &lt;a href="https://www.stainless.com/docs/guides/iterate-with-lsp/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for detailed setup instructions. If you run into issues or have feedback, reach out to &lt;a href="mailto:support@stainless.com"&gt;support@stainless.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>lsp</category>
      <category>stainless</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Stainless CLI generator: your API, now with `--help`</title>
      <dc:creator>CJ Avilla</dc:creator>
      <pubDate>Mon, 09 Feb 2026 16:14:14 +0000</pubDate>
      <link>https://dev.to/stainlessapi/stainless-cli-generator-your-api-now-with-help-13hp</link>
      <guid>https://dev.to/stainlessapi/stainless-cli-generator-your-api-now-with-help-13hp</guid>
      <description>&lt;p&gt;You can now generate a command line tool for your API directly from your OpenAPI spec with Stainless. Enable the CLI target in the Studio to generate a command line interface with proper argument parsing, automatic pagination, and the documentation features developers expect.&lt;/p&gt;

&lt;p&gt;Command line tools are one of the fastest ways for developers (and agents!) to interact with APIs.  They're scriptable, composable, and ideal for quick experimentation and iteration.  Building a high-quality CLI tool that people actually want to use, however, requires a lot of work.  You need to get a lot of things right, like output formatting, pagination, shell completions, manpages, cross-platform distribution, and more.  Due to the amount of time and effort that’s normally required, many API providers either don’t provide a CLI or only provide a minimal, watered-down tool that developers find wanting.&lt;/p&gt;

&lt;p&gt;With today's release, Stainless makes supporting the CLI easier than ever.  You can generate a CLI tool from the same spec that powers your SDKs and keep everything in sync automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go, CLI, Go!
&lt;/h2&gt;

&lt;p&gt;Under the hood, when we generate a CLI tool for your API, it’s actually a wrapper around your Go SDK.&lt;/p&gt;

&lt;p&gt;A common question among our earliest CLI users was, “why Go?” It’s a great question! We chose Go for several reasons, but most of them boil down to the high performance and ease of distribution Go provides.&lt;/p&gt;

&lt;p&gt;Go compiles to native binaries with no external runtime dependencies and near-instant startup time. Your users don't have to have Go installed, all they see is a fast, efficient, and native executable.&lt;/p&gt;

&lt;p&gt;Go also has excellent cross-compilation support, which makes it easy for you to ship builds for macOS, Linux, and Windows across multiple architectures. That makes distribution straightforward for you, and installation easy for your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;Every CLI tool we generate follows a resource-based structure that mirrors your API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;your-project &lt;span class="o"&gt;[&lt;/span&gt;resource &lt;span class="o"&gt;[&lt;/span&gt;sub-resource...]] method-name &lt;span class="nt"&gt;--method-arg&lt;/span&gt; value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, with an API that manages people, using the CLI would look like this:&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;# Retrieve a person by ID&lt;/span&gt;
your-project people retrieve &lt;span class="nt"&gt;--id&lt;/span&gt; 123

&lt;span class="c"&gt;# Create a new person&lt;/span&gt;
your-project people create &lt;span class="nt"&gt;--job&lt;/span&gt; &lt;span class="s2"&gt;"President"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt;.full-name &lt;span class="s2"&gt;"Abraham Lincoln"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt;.nickname &lt;span class="s2"&gt;"Abe Lincoln"&lt;/span&gt;

&lt;span class="c"&gt;# List all people (paginated automatically)&lt;/span&gt;
your-project people list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method names and flags use kebab-case, the &lt;code&gt;--help&lt;/code&gt; flag gives you full documentation for any command, and man pages are generated automatically so &lt;code&gt;man your-project&lt;/code&gt; will work out of the box for your end users.&lt;/p&gt;

&lt;p&gt;Shell completions are included for Bash, Zsh, fish, and PowerShell, and the CLI also works on Windows using the standard &lt;code&gt;--flag=value&lt;/code&gt; syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argumentative arguments
&lt;/h2&gt;

&lt;p&gt;Argument parsing is one of those problems that looks simple until you try to create a polished implementation. Shell scripting has many quirks that make complex payloads awkward. JSON requires nested quoting that's easy to mess up. If you want tab completion and proper &lt;code&gt;--help&lt;/code&gt; documentation, your flags need to be defined statically (which rules out infinitely nested paths like &lt;code&gt;--messages.0.content.0.text&lt;/code&gt;).And so on.&lt;/p&gt;

&lt;p&gt;We spent a lot of time working through the tradeoffs. The design we landed on optimizes common cases, but handles complex structures with grace when you need them.&lt;/p&gt;

&lt;p&gt;Pass simple values with intuitive syntax familiar to command line users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;your-project &lt;span class="nb"&gt;users &lt;/span&gt;create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Alice"&lt;/span&gt; &lt;span class="nt"&gt;--role&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use dot notation for nested objects up to two levels deep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;your-project people create &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt;.first &lt;span class="s2"&gt;"Abraham"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt;.last &lt;span class="s2"&gt;"Lincoln"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--address&lt;/span&gt;.city &lt;span class="s2"&gt;"Springfield"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anything deeper, or for complex polymorphic types, pass YAML or JSON inline:&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;# YAML&lt;/span&gt;
  your-project people create &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s1"&gt;'first: Abraham, last: Lincoln'&lt;/span&gt;

  &lt;span class="c"&gt;# JSON&lt;/span&gt;
  your-project people create &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s1"&gt;'{"first": "Abraham", "last": "Lincoln"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass array values with repeated flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;your-project &lt;span class="nb"&gt;users &lt;/span&gt;update &lt;span class="nt"&gt;--tag&lt;/span&gt; admin &lt;span class="nt"&gt;--tag&lt;/span&gt; reviewer &lt;span class="nt"&gt;--tag&lt;/span&gt; owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pipe full payloads via stdin. JSON and YAML both work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;your-project people create &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;
name:
  full_name: Abraham Lincoln
  nickname: Honest Abe
job: President
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also combine piped input with flags. Flags override values from stdin, which is useful for templating:&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;cat &lt;/span&gt;base-user.json | your-project &lt;span class="nb"&gt;users &lt;/span&gt;create &lt;span class="nt"&gt;--role&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Built for AI agents
&lt;/h2&gt;

&lt;p&gt;CLI tools make great interfaces for agents.&lt;/p&gt;

&lt;p&gt;When an agent uses raw HTTP, it has to construct headers, manage authentication, serialize request bodies, and parse responses. A CLI takes care of all of that: authentication is handled using environment variables, request bodies become flags, and responses come back as structured JSON.  All by default!&lt;/p&gt;

&lt;p&gt;And, perhaps most critically, a CLI tool is self-documenting. An agent can run &lt;code&gt;--help&lt;/code&gt; to discover what's available.&lt;/p&gt;

&lt;p&gt;We tested this with a CLI generated for the Spotify API. Claude Code ran &lt;code&gt;--help&lt;/code&gt; to discover the available commands, constructed valid requests, and parsed the responses without needing external documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=sXH3WBYMKFw" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=sXH3WBYMKFw&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same properties that make CLIs good for humans also apply to agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable structure: &lt;code&gt;resource method --flag value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Self-documenting: &lt;code&gt;--help&lt;/code&gt; on every command&lt;/li&gt;
&lt;li&gt;Good error messages: clear feedback when something's wrong and suggestions for mistyped command names or flags&lt;/li&gt;
&lt;li&gt;Standard conventions: kebab-case flags, JSON output, meaningful exit codes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Robust distribution
&lt;/h2&gt;

&lt;p&gt;Building a binary is one thing, but distributing it across platforms, setting up package managers, and keeping versions in sync with your API changes is quite another.&lt;/p&gt;

&lt;p&gt;The Stainless CLI generator plugs into the same release flow as your other Stainless SDKs. When you merge a release PR, we use &lt;a href="https://goreleaser.com" rel="noopener noreferrer"&gt;GoReleaser&lt;/a&gt; to automatically build binaries for macOS (arm64 and amd64), Linux (arm64, amd64, 386), and Windows (arm64, amd64, 386), and each release publishes to GitHub with all compiled binaries.&lt;/p&gt;

&lt;p&gt;For macOS users, we support Homebrew out of the box. Configure your tap repository and Stainless keeps the formula updated automatically. Your users can install your CLI with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;your-org/tools/your-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're also working on support for more package managers, like npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started today
&lt;/h2&gt;

&lt;p&gt;Enable the CLI target in your Stainless config alongside your Go SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;go&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;cli&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;binary_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-project&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out our &lt;a href="https://www.stainless.com/docs/guides/cli-generator" rel="noopener noreferrer"&gt;CLI generator docs&lt;/a&gt; for more details on setup and configuration. If you have any feedback, please reach out to &lt;a href="mailto:support@stainless.com"&gt;support@stainless.com&lt;/a&gt;. We'd love to hear what you think!&lt;/p&gt;

</description>
      <category>stainless</category>
      <category>cli</category>
      <category>openapi</category>
    </item>
    <item>
      <title>How to generate a TypeScript SDK from your OpenAPI spec</title>
      <dc:creator>CJ Avilla</dc:creator>
      <pubDate>Mon, 09 Feb 2026 15:47:45 +0000</pubDate>
      <link>https://dev.to/stainlessapi/how-to-generate-a-typescript-sdk-from-your-openapi-spec-4m8j</link>
      <guid>https://dev.to/stainlessapi/how-to-generate-a-typescript-sdk-from-your-openapi-spec-4m8j</guid>
      <description>&lt;p&gt;If you maintain a public API, you've probably faced the same question: how do you help developers (and agents) integrate quickly without forcing them to hand-craft HTTP requests, parse JSON responses, and handle edge cases on their own?&lt;/p&gt;

&lt;p&gt;The answer is a platform, not a single artifact. SDKs, documentation, and other tools work together to shape how developers experience your API. &lt;/p&gt;

&lt;p&gt;This guide focuses on one component: the TypeScript SDK. For JavaScript and TypeScript developers, it is often the deciding factor between choosing your API or moving on to a competitor with better tooling.&lt;/p&gt;

&lt;p&gt;This guide covers how to generate a TypeScript SDK from an OpenAPI specification. It covers the traditional approaches, where they fall short, and how to produce a production-ready SDK that feels hand-crafted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a TypeScript SDK matters for your API
&lt;/h2&gt;

&lt;p&gt;Without an SDK, developers must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read your API documentation and manually construct HTTP requests&lt;/li&gt;
&lt;li&gt;Handle authentication headers, query parameter serialization, and request body formatting&lt;/li&gt;
&lt;li&gt;Parse responses and map them to their own types&lt;/li&gt;
&lt;li&gt;Implement error handling, retries, and pagination logic from scratch&lt;/li&gt;
&lt;li&gt;Keep their integration code updated as your API evolves&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-designed TypeScript SDK eliminates all of this. Developers get type-safe methods with autocomplete, compile-time error checking, and built-in handling for common patterns.&lt;/p&gt;

&lt;p&gt;Integration time drops significantly.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqsf75xf3hxcehkafjhl.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqsf75xf3hxcehkafjhl.png" alt="Raw fetch vs. using an SDK" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The payoff is faster adoption and fewer support tickets, especially when something goes wrong. Strong TypeScript types make it easier for developers and agents to detect, understand, and recover from incorrect usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The traditional approach: OpenAPI Generator
&lt;/h2&gt;

&lt;p&gt;The legacy starting point is the open-source OpenAPI Generator. It supports dozens of languages, including TypeScript, and can scaffold a client library from your spec in minutes.&lt;/p&gt;

&lt;p&gt;Here's the typical workflow:&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;# Generate a TypeScript SDK using the fetch template&lt;/span&gt;
openapi-generator-cli generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; path/to/openapi.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-g&lt;/span&gt; typescript-fetch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ./generated-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a functional SDK with TypeScript interfaces for your models and a client class with methods for each endpoint.&lt;/p&gt;

&lt;p&gt;For internal tools or quick prototypes, this works. For a public-facing SDK that represents your product, the limitations become apparent quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where basic code generation falls short
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Generic, non-idiomatic code.&lt;/strong&gt; The templates produce functional output, but method names, class structures, and patterns don't always match what TypeScript developers expect. The code feels generated, not designed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing advanced features.&lt;/strong&gt; Real APIs need pagination, retries with exponential backoff, and structured error handling. OpenAPI Generator doesn't include these. Your users must implement them manually, which leads to inconsistent behavior across integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual maintenance burden.&lt;/strong&gt; Every time your API changes, you re-run the generator, manually bump versions, and publish to &lt;code&gt;npm&lt;/code&gt;. There's no built-in handling for breaking changes or automated release workflows. The onus is entirely on your team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limited customization.&lt;/strong&gt; Tweaking the output requires forking Mustache templates, which quickly becomes a maintenance project of its own.&lt;/p&gt;

&lt;p&gt;We designed Stainless for teams that run into these limits. It takes the same OpenAPI input, but optimizes for production SDKs rather than scaffolding. The table below compares the two approaches.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;OpenAPI Generator&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Stainless&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic (&lt;code&gt;ResponseError&lt;/code&gt;, &lt;code&gt;FetchError&lt;/code&gt;, &lt;code&gt;RequiredError&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Rich hierarchy with many specific error types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry Logic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None built-in&lt;/td&gt;
&lt;td&gt;Exponential backoff with jitter, Retry-After support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pagination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Returns raw paginated response&lt;/td&gt;
&lt;td&gt;Auto-pagination with async iterators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Streaming (SSE)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Full SSE parser with &lt;code&gt;Stream&lt;/code&gt; class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Ergonomics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verbose method names, single object params&lt;/td&gt;
&lt;td&gt;Clean method names, positional + optional params&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good (&lt;code&gt;FromJSON&lt;/code&gt; / &lt;code&gt;ToJSON&lt;/code&gt; transforms)&lt;/td&gt;
&lt;td&gt;Excellent (strict mode, exactOptionalPropertyTypes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Middleware Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pre/post/onError middleware&lt;/td&gt;
&lt;td&gt;Request options per-call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Uploads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic FormData/Blob&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Uploadable&lt;/code&gt; type (&lt;code&gt;File&lt;/code&gt;, &lt;code&gt;Response&lt;/code&gt;, &lt;code&gt;ReadStream&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JSDoc from OpenAPI spec&lt;/td&gt;
&lt;td&gt;Rich TSDoc with code examples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw Response Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via ApiResponse wrapper&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.asResponse()&lt;/code&gt; and &lt;code&gt;.withResponse()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Environment Config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;basePath&lt;/code&gt; in Configuration&lt;/td&gt;
&lt;td&gt;Multiple named environments and env vars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Module System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single export pattern&lt;/td&gt;
&lt;td&gt;Dual ESM/CJS with conditional exports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to generate a TypeScript SDK from OpenAPI with Stainless: the recommended approach
&lt;/h2&gt;

&lt;p&gt;Stainless generates TypeScript SDKs from OpenAPI that follow idiomatic language conventions and include production features like retries, pagination, and structured errors by default.&lt;/p&gt;

&lt;p&gt;This approach comes from experience building large-scale SDK platforms, including work on Stripe’s SDK infrastructure, where maintaining high-quality, hand-crafted SDKs across many languages required far more than mapping endpoints to methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;You can create a new project from an OpenAPI spec in about one minute:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sign up for Stainless.&lt;/strong&gt; Create an account at &lt;a href="http://app.stainless.com" rel="noopener noreferrer"&gt;app.stainless.com&lt;/a&gt; using your GitHub account, then create an organization for yourself or your company.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a project.&lt;/strong&gt; Create a new Stainless project for your API by providing an OpenAPI spec. You can paste a URL to a hosted spec, upload a file, or start from an example such as the Petstore API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: A Stainless &lt;strong&gt;project&lt;/strong&gt; represents a single API. From one project, you can generate and manage multiple SDKs in different languages, along with related artifacts such as documentation sites, CLIs, MCP servers, or Terraform providers, all driven from the same OpenAPI spec.&lt;/p&gt;

&lt;p&gt;Once you create the project project, Stainless generates a TypeScript SDK and opens it in the Studio inside the dashboard. Stainless also invites you to a GitHub repository containing the generated SDK code.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feoa6hflpy6vlwsfsxg83.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feoa6hflpy6vlwsfsxg83.png" alt="Stainless studio for viewing an SDK" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What you get out of the box
&lt;/h3&gt;

&lt;p&gt;The generated TypeScript SDK includes retry logic, streaming, pagination, and error handling that you would otherwise build and maintain yourself. Here's what that looks like in practice, using the &lt;a href="https://github.com/openai/openai-node" rel="noopener noreferrer"&gt;OpenAI Node SDK&lt;/a&gt; as an example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero dependencies&lt;/strong&gt;. Stainless uses the built-in &lt;code&gt;fetch&lt;/code&gt; API for HTTP requests. The SDK works in Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge Runtime, and modern browsers without pulling in a single dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full type safety&lt;/strong&gt;. Every model, request, and response is fully typed. Developers get autocomplete for all parameters and compile-time validation of their API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="s1"&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="c1"&gt;// Uses OPENAI_API_KEY env var by default&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-5.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a coding assistant that talks like a pirate&lt;/span&gt;&lt;span class="dl"&gt;'&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="s1"&gt;Are semicolons optional in JavaScript?&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="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;response&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;&lt;strong&gt;Streaming (SSE)&lt;/strong&gt;. For APIs that support Server-Sent Events, the SDK provides a &lt;code&gt;Stream&lt;/code&gt; class with async iteration built in. No manual SSE parsing required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-5.2&lt;/span&gt;&lt;span class="dl"&gt;'&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="s1"&gt;Say "Sheep sleep deep" ten times fast!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&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="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;stream&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;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auto-pagination&lt;/strong&gt;. List endpoints return async iterators that handle page tokens automatically. Developers don't need to write pagination logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Automatically fetches more pages as needed.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;job&lt;/span&gt; &lt;span class="k"&gt;of&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;fineTuning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Or work with a single page at a time:&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&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;fineTuning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;job&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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;job&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Automatic retries&lt;/strong&gt;. The SDK automatically retries for connection errors, &lt;code&gt;408&lt;/code&gt; timeouts, &lt;code&gt;429&lt;/code&gt; rate limits, and &lt;code&gt;5xx&lt;/code&gt; server errors with exponential backoff. Developers can configure this globally or per-request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Configure the default for all requests:&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;maxRetries&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="c1"&gt;// default is 2&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Or override per-request:&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-5.2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;messages&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="s1"&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="s1"&gt;Hello!&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring, publishing, and keeping SDKs in sync
&lt;/h3&gt;

&lt;p&gt;Beyond what's generated out of the box, Stainless handles the operational side of SDK management:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SDK configuration&lt;/strong&gt;. A &lt;code&gt;stainless.yaml&lt;/code&gt; file controls how your API maps to SDK methods: resource grouping, pagination schemes, method naming, and more. The dashboard includes an LSP-powered editor with autocomplete and &lt;a href="https://stainless.com/docs/guides/configure" rel="noopener noreferrer"&gt;inline documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated publishing&lt;/strong&gt;. Connect your &lt;code&gt;npm&lt;/code&gt; account and new SDK versions publish automatically when your API spec changes. Stainless &lt;a href="https://stainless.com/docs/guides/publish" rel="noopener noreferrer"&gt;supports&lt;/a&gt; secure OIDC-based trusted publishing through GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated regeneration&lt;/strong&gt;. A GitHub Action in your API repository &lt;a href="https://stainless.com/docs/guides/preview-builds/" rel="noopener noreferrer"&gt;keeps SDKs in sync automatically&lt;/a&gt;: open a PR that changes your spec and Stainless previews the SDK diff; merge it and release PRs are opened across all your SDK repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;p&gt;Stainless generates the official, production SDKs for several major API providers, collectively handling millions of API calls daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/openai/openai-node" rel="noopener noreferrer"&gt;OpenAI Node SDK&lt;/a&gt;: millions of weekly downloads&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/anthropic-sdk-typescript" rel="noopener noreferrer"&gt;Anthropic TypeScript SDK&lt;/a&gt;: powers Claude integrations&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lithic-com/lithic-node" rel="noopener noreferrer"&gt;Lithic Node SDK&lt;/a&gt;: card issuing and payment processing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Modern-Treasury/modern-treasury-node" rel="noopener noreferrer"&gt;Modern Treasury Node SDK&lt;/a&gt;: payment operations platform&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Basic code generation tools work for internal tools or prototypes. For a public SDK that represents your product, you need production-ready features: zero dependencies, full type safety, built-in pagination and retries, and automated publishing.&lt;/p&gt;

&lt;p&gt;Stainless generates TypeScript SDKs with these features built in. The result is faster adoption, fewer support tickets, and a stronger API platform.&lt;/p&gt;

&lt;p&gt;Ready to generate your TypeScript SDK? &lt;a href="https://app.stainless.com/" rel="noopener noreferrer"&gt;Get started with Stainless&lt;/a&gt; or &lt;a href="https://stainless.com/docs/targets/typescript" rel="noopener noreferrer"&gt;read the documentation&lt;/a&gt; to learn more.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>typescript</category>
      <category>stainless</category>
    </item>
  </channel>
</rss>
