<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Scalar]]></title><description><![CDATA[The modern open-source developer experience platform for your APIs.]]></description><link>https://blog.scalar.com</link><image><url>https://substackcdn.com/image/fetch/$s_!dtI3!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d577153-7bcc-4fae-83cc-800387da98c8_200x200.png</url><title>Scalar</title><link>https://blog.scalar.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 27 Apr 2026 12:20:34 GMT</lastBuildDate><atom:link href="https://blog.scalar.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Marc]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[marclave@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[marclave@substack.com]]></itunes:email><itunes:name><![CDATA[Marc]]></itunes:name></itunes:owner><itunes:author><![CDATA[Marc]]></itunes:author><googleplay:owner><![CDATA[marclave@substack.com]]></googleplay:owner><googleplay:email><![CDATA[marclave@substack.com]]></googleplay:email><googleplay:author><![CDATA[Marc]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[How to set up an OpenAPI mock server]]></title><description><![CDATA[How the Scalar creates APIs you can&#8217;t believe aren&#8217;t real]]></description><link>https://blog.scalar.com/p/how-to-set-up-an-openapi-mock-server</link><guid isPermaLink="false">https://blog.scalar.com/p/how-to-set-up-an-openapi-mock-server</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Tue, 19 Aug 2025 17:23:16 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ad4e87ab-91cb-4429-b7ca-812afe2fb0d4_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To create a mock server for your OpenAPI document, download the <a href="https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md">Scalar CLI</a>:</p><pre><code><code>npm -g install @scalar/cli</code></code></pre><blockquote><p><strong>Note:</strong> There&#8217;s another <code>scalar</code> CLI which is bundled with Git. If you run into naming conflicts and never use the other CLI anyway, you can replace it like this:</p><pre><code><code>npm -g --force install @scalar/cli</code></code></pre></blockquote><p>Once installed, run the <code>document mock</code> command pointing at your OpenAPI document (no matter if it&#8217;s JSON or YAML, local or remote).</p><pre><code><code>scalar document mock https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/3.1.json</code></code></pre><p>This launches a fully functional HTTP server that responds to every endpoint in your OpenAPI file with realistic mock data based on your schema definitions. You&#8217;ll see a color-coded output of available paths for you to make requests to:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2R58!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2R58!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 424w, https://substackcdn.com/image/fetch/$s_!2R58!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 848w, https://substackcdn.com/image/fetch/$s_!2R58!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 1272w, https://substackcdn.com/image/fetch/$s_!2R58!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2R58!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png" width="1456" height="881" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:881,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:253136,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/171302044?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2R58!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 424w, https://substackcdn.com/image/fetch/$s_!2R58!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 848w, https://substackcdn.com/image/fetch/$s_!2R58!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 1272w, https://substackcdn.com/image/fetch/$s_!2R58!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b371a54-a7ef-4d61-9814-674e728b26b0_2316x1402.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When you make those requests, you&#8217;ll get responses as if that server exists:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U65N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U65N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 424w, https://substackcdn.com/image/fetch/$s_!U65N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 848w, https://substackcdn.com/image/fetch/$s_!U65N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 1272w, https://substackcdn.com/image/fetch/$s_!U65N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U65N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png" width="1178" height="700" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a671e140-f544-4418-9987-213226ede16b_1178x700.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:700,&quot;width&quot;:1178,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96374,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/171302044?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U65N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 424w, https://substackcdn.com/image/fetch/$s_!U65N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 848w, https://substackcdn.com/image/fetch/$s_!U65N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 1272w, https://substackcdn.com/image/fetch/$s_!U65N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa671e140-f544-4418-9987-213226ede16b_1178x700.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The mock server also has a few options to customize it:</p><ul><li><p><code>--port</code> to change the port from the default of <code>3000</code>.</p></li><li><p><code>--watch</code> to watch for file changes and reload when you modify the OpenAPI file.</p></li><li><p><code>--once</code> to run tests once. Boots the server, responds to requests, and exits. Perfect for CI pipelines.</p></li></ul><h2>Why does OpenAPI mocking matter anyways?</h2><p>Mock servers are more than just &#8220;fake APIs.&#8221; They unlock workflows that speed up development and reduce dependencies.</p><ol><li><p><strong>Parallel development:</strong> Frontend and backend teams can work independently without needing to wait for the real API to be built.</p></li><li><p><strong>Testing without risk:</strong> Safely run tests and automations against the mock server without hitting production or staging (and the real errors that come with them).</p></li><li><p><strong>Faster prototyping:</strong> Build and validate API contracts early in the process, before you&#8217;ve written any business logic.</p></li><li><p><strong>Fewer third-party dependencies:</strong> Developers can work with third-party APIs without their rate limits, costs, and availability constraints.</p></li></ol><p>A good mock server gives you confidence in the API while keeping the whole team moving.</p><h2>How does Scalar&#8217;s OpenAPI mock server work under the hood?</h2><p>Under the hood, the CLI relies on Scalar&#8217;s open source <code>mock-server</code> <a href="https://github.com/scalar/scalar/tree/main/packages/mock-server">package</a> to generate routes and serve requests.</p><p>The whole request flow looks like this:</p><ol><li><p>The Scalar CLI detects whether your input is a file or URL then loads, dereferences, and <a href="https://blog.scalar.com/p/how-to-do-openapi-validation-and">validates</a> it with the <code>@scalar/openapi-parser</code> <a href="https://github.com/scalar/scalar/tree/main/packages/openapi-parser">package</a>.</p></li><li><p>With the OpenAPI doc, the <code>mock-server</code> package generates routes for each path in Hono syntax and responses based on schemas. It also validates parameters, sets up authentication (if <a href="https://blog.scalar.com/p/a-guide-to-openapi-security-and-how">security schemes</a> are defined), and returns appropriate HTTP status codes.</p></li><li><p>The generated routes are then served using Hono, a lightweight, fast HTTP framework. Hono is responsible for route matching, authentication checking, parameter extraction, and mock response generation.</p></li><li><p>The mock responses use Scalar&#8217;s open source <code>oas-utils</code> <a href="https://github.com/scalar/scalar/tree/main/packages/oas-utils">package</a> to generate realistic data from OpenAPI schemas. This uses examples when available.</p></li><li><p>The generated data respects schema constraints like data types, formats, and validation rules to create mock responses that match your API specification.</p></li></ol><p>All this gives you a fast, spec-compliant, developer-friendly mock server you can start in seconds.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Mocked APIs might not be real, but this subscribe form sure is&#8230;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How to do OpenAPI validation (and why it matters)]]></title><description><![CDATA[How OpenAPI validation works with Scalar]]></description><link>https://blog.scalar.com/p/how-to-do-openapi-validation-and</link><guid isPermaLink="false">https://blog.scalar.com/p/how-to-do-openapi-validation-and</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Mon, 07 Jul 2025 17:02:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/159ee06a-45be-49b3-b715-478622b51e9d_1456x1050.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To validate your OpenAPI document, download the <a href="https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md">Scalar CLI</a>:</p><pre><code><code>npm -g install @scalar/cli
</code></code></pre><blockquote><p><strong>Note:</strong> There&#8217;s another <code>scalar</code> CLI which is bundled with Git. If you run into naming conflicts, but never use the other CLI anyway, you can replace it like this:</p><pre><code><code>npm -g --force install @scalar/cli
</code></code></pre></blockquote><p>Once installed, run the <code>document validate</code> command pointed at your OpenAPI document (no matter if it&#8217;s <code>JSON</code> or <code>YAML</code>).</p><pre><code><code>scalar document validate galaxy.json
</code></code></pre><p>Now you have a validated OpenAPI document you can safely use however you like (or one that needs fixing &#128517;).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ewm-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ewm-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 424w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 848w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 1272w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ewm-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png" width="1303" height="1056" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1056,&quot;width&quot;:1303,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:211189,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/167652652?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ewm-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 424w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 848w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 1272w, https://substackcdn.com/image/fetch/$s_!Ewm-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffbc18eed-3e63-4d56-8c5d-67928241341a_1303x1056.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Fun fact:</strong> Scalar&#8217;s <a href="https://docs.scalar.com/">OpenAPI editor</a> has validation built-in. No command line required.</p></blockquote><h2>Why does OpenAPI validation matter anyways?</h2><p>Not sure why you&#8217;d do the above? You&#8217;ve come to the right place. The benefits of validating your OpenAPI doc include:</p><ul><li><p><strong>Prevents runtime errors and bad devex</strong>. Invalid or incomplete docs can result in confused users, broken test environments, incorrect references, and frustrated devs.</p></li><li><p><strong>Ensures standards compliance.</strong> Tools in the OpenAPI ecosystem (like Scalar) often require your doc to fit the standard. Tools might reject your doc if invalid, or worse, break when you try to use an invalid one.</p></li><li><p><strong>Enables automation.</strong> Automatically validating your OpenAPI doc unlocks automation elsewhere. You won&#8217;t need to manually upload the doc to generate mock servers, tests, codegen, and related areas of your CI/CD pipeline. All of it <a href="https://www.youtube.com/watch?v=nVqcxarP9J4&amp;pp=0gcJCfwAo7VqN5tD">just works</a>.</p></li><li><p><strong>Catches breaking changes.</strong> If you have internal API standards, validating an OpenAPI doc against them can ensure these standards aren&#8217;t broken. This can be critical for proper API governance.</p></li></ul><h2>How does Scalar&#8217;s OpenAPI validation work?</h2><p>Can&#8217;t get enough about OpenAPI validation, huh? Well here&#8217;s how it works under the hood.</p><p>We love two things at Scalar: open source and OpenAPI. This means we&#8217;ve built a bunch of tools to work with OpenAPI and they are all open source.</p><p>Most important for validation is the <code>openapi-parser</code><a href="https://github.com/scalar/scalar/tree/main/packages/openapi-parser"> package</a>. It is what the CLI is relying on to actually do the validation. Once you run the command, the process looks like this:</p><ul><li><p>The parser loads the doc and ensures all <code>$ref</code> pointers are correctly resolved. These could be remote URLs needing fetching or local files needing to be read. It then replaces the references in the doc and creates a filesystem object containing the parsed doc.</p></li><li><p>It then checks the version then uses AJV (Another JSON Validator) to check against the official <a href="https://github.com/scalar/scalar/tree/main/packages/openapi-parser/src/schemas">OpenAPI JSON schema</a>. There is specific validation that happens for different versions of OpenAPI.</p></li><li><p>Once done, the CLI either returns a nice success message or transforms the AJV errors into a user-readable one.</p></li></ul><p>All this is packaged into a simple to use CLI (with <a href="https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md">much more functionality</a>). Also, because the <code>openapi-parser</code> is <a href="https://github.com/scalar/scalar/tree/main/packages/openapi-parser">open source</a>, if you wanted to implement (or modify) validation yourself, you could!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Help me validate this form is working and subscribe &#128521;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How we created an animated, responsive README]]></title><description><![CDATA[For an open source project, your README is your landing page.]]></description><link>https://blog.scalar.com/p/how-we-created-an-animated-responsive</link><guid isPermaLink="false">https://blog.scalar.com/p/how-we-created-an-animated-responsive</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 28 May 2025 15:08:18 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3b1b57d2-cea0-4d23-a045-4b730e9e32cc_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For an open source project, your README is your landing page. Using it to stand out makes a big impact on convincing developers to explore and adopt your tool. It is a window into your app (and you want users to enter it).</p><p>READMEs are written in Markdown, a format that is intentionally simple in its design. Usually, you are working with a very limited set of tools like text, bullet points, and images. Luckily, <a href="https://github.github.com/gfm/">GitHub&#8217;s flavor of Markdown</a> provides a lot more than people realize (like a lot of HTML being supported).</p><p>At Scalar, we take full advantage of this to create responsive and animated elements that make <a href="https://github.com/scalar/scalar/blob/main/README.md">our README</a> pop. Here&#8217;s all the details:</p><h2><strong>Creating responsive images</strong></h2><p>The Scalar app is responsive, why shouldn&#8217;t our images be? An API client or API reference has a ton of information included in it. For you to see all that detail, you need the right size of images. One size does not fit all.</p><p>Normal Markdown images only enable you to put one size. Even if we used the <code>&lt;img&gt;</code> HTML component, we&#8217;d still be in the same boat. SVGs on the other hand, enable us to do cool stuff like this:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;4d206e1a-f568-45c6-bc82-70aefbc9f246&quot;,&quot;duration&quot;:null}"></div><p>This works by being an SVG with a <code>&lt;foreignObject&gt;</code> inside of it and <code>&lt;html&gt;</code> inside of that. The image remains centered in its container and is able to use CSS to control the display of the potential images. Here is a simplified example of how the code looks:</p><pre><code>&lt;svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 500 500"&gt;
  &lt;foreignObject width="100%" height="100%"&gt;
    &lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: center; justify-content: center; height: 100%;"&gt;
      &lt;img src="desktop-image.jpg" style="width: 100%; max-width: 400px;"&gt;
      &lt;img src="mobile-image.jpg" style="width: 100%; max-width: 400px; display: none;"&gt;
      &lt;style&gt;
        @media (max-width: 450px) {
          img:first-of-type { display: none; }
          img:last-of-type { display: block; }
        }
      &lt;/style&gt;
    &lt;/div&gt;
  &lt;/foreignObject&gt;
&lt;/svg&gt;</code></pre><p>And we didn&#8217;t just create one of these SVGs, we did two: one for light mode and the other for dark. Once you accept the fact that people use GitHub in light mode, you&#8217;ll probably not want to blind them with your images in the mode they choose.</p><p>Luckily, GitHub makes this a lot easier than responsive images. You simply need to add <code>#gh-light-mode-only</code> and <code>#gh-dark-mode-only</code> tags to add to the images like we do here:</p><pre><code>&#9;&lt;img width="830" height="520" src="https://github.com/user-attachments/assets/7c4d4971-a6d9-457d-a7ab-11894889f6f9#gh-light-mode-only"&gt;
&#9;&lt;img width="830" height="520" src="https://github.com/user-attachments/assets/0e3ffca5-8912-487a-a390-fef0e8222c35#gh-dark-mode-only"&gt;</code></pre><h2><strong>Adding Real ASCII in real Markdown</strong></h2><p>As a tool built for developers, we&#8217;re big fans of the ultimate art form of developers: ASCII. We use ASCII art in a bunch of places in our app, but unfortunately, Markdown doesn&#8217;t handle it well.</p><p>Instead, we use SVGs with foreign objects (again). This lets us do this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UG1o!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UG1o!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 424w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 848w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 1272w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UG1o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png" width="1456" height="525" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:525,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;ASCII is cool&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ASCII is cool" title="ASCII is cool" srcset="https://substackcdn.com/image/fetch/$s_!UG1o!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 424w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 848w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 1272w, https://substackcdn.com/image/fetch/$s_!UG1o!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84daff85-d5f4-4ed2-b403-fd72d6b86f4b_1768x638.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As you can see, this enables three things:</p><ul><li><p>Use ASCII in our README</p></li><li><p>Have a horizontal split for our two different products</p></li><li><p>Have both link to the correct spots</p></li></ul><p>This is done with a combination of HTML (for the links) and SVGs that look like this:</p><pre><code>&lt;svg xmlns="http://www.w3.org/2000/svg"&gt;
    &lt;foreignObject width="100%" height="100%"&gt;&lt;script xmlns=""/&gt;
        &lt;div xmlns="http://www.w3.org/1999/xhtml" class="fixme"&gt;
            &lt;div class="markdown-body"&gt;
                &lt;pre class="markdown-code"&gt;

 +#INTRODUCING#.
 +#SCALAR##API#-
        -CLIENT-
      -TEST -UR-
    -FAV#   -##-
  -APIs     -##-
-LETS       -++-
.GO-
          &lt;/pre&gt;
                &lt;h3&gt;API Client&lt;/h3&gt;
                &lt;p xmlns="http://www.w3.org/1999/xhtml"&gt;Offline first API Client &lt;span class="mobile-copy"&gt; built for
                        OpenAPI.&lt;/span&gt;&lt;/p&gt;
                &lt;p class="linkcolor"&gt;Download&lt;/p&gt;
            &lt;/div&gt;
            &lt;style&gt;
                * {
                    margin: 0;
                    padding: 0;
                    box-sizing: border-box;
                }

                .markdown-code {
                    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
                    white-space: pre;
                    font-size: 0.5rem;
                }
                
                &lt;!-- more styles... --&gt;
            &lt;/style&gt;
        &lt;/div&gt;
    &lt;/foreignObject&gt;
&lt;script xmlns=""/&gt;&lt;/svg&gt;</code></pre><h2><strong>Creating animations that will knock your socks off</strong></h2><p>Now, all this is cool so far, but you could mistake it for any other GitHub repo. Something that would be totally unique to use is some sweet, sweet animations running in our GitHub repo.</p><p>We love our contributors. What better way to credit them than an animated shout out in our README? Contribution is a key piece of being open source after all. Again, we use SVGs and <code>foreignobjects</code>.</p><p>Our first iteration of this was <a href="https://github.com/scalar/scalar/pull/3318">a pillar in a room with each of our contributors projected on it</a>.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;c86ad1e3-d784-4ecb-bbb0-45c0c1683191&quot;,&quot;duration&quot;:null}"></div><p>This works by:</p><ul><li><p>Creating &#8220;walls&#8221; using background textures and rotations.</p></li><li><p>Defining a 3D, rotating cube with ASCII-style avatars with commit info.</p></li><li><p>More ASCII art with usernames, number of commits, and line additions/deletions.</p></li><li><p>A CSS scroll animation to scroll this ASCII art continually upward</p></li></ul><p>Eventually, we got bored of this and moved airplanes. The airplanes are HTML and they are animated with CSS.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;d019aaef-0d2f-403a-adb8-52a685260e1e&quot;,&quot;duration&quot;:null}"></div><p>This works by:</p><ul><li><p>Creating two banners with the contributor name, commit count, and stats as well as the ASCII art of an airplane.</p></li><li><p>Using <code>@keyframes</code>, they move from left to right continuously at slightly different offset speeds.</p></li><li><p>The plane bounces continuously by repeatedly fading in and out of slightly different planes.</p></li><li><p>The banner contributor text and color change based on <code>@keyframes</code> as well.</p></li></ul><blockquote><p><strong>Why not use a gif?</strong> Other than people not knowing how it is pronounced, gifs have worse performance and are honestly more difficult to make for developers used to coding animations by hand. Since we can show off our programming chops, why not do it?</p></blockquote><h2><strong>Why all this work to make our README look cool?</strong></h2><ol><li><p>It&#8217;s fun.</p></li><li><p>It makes us stand out.</p></li></ol><p>When so many READMEs are the same, one that plays with the limits of it as a form really stands out. As an open source project reliant on our GitHub repo to attract and convince users, this makes a massive difference. We hope our experience will be helpful if you want to do the same.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">READMEs are cool, but so is being able to READMORE of our blog by subscribing</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Scalar themes work]]></title><description><![CDATA[Every product can be thought of as a combination of three things: data, functionality, and display.]]></description><link>https://blog.scalar.com/p/how-scalar-themes-work</link><guid isPermaLink="false">https://blog.scalar.com/p/how-scalar-themes-work</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 07 May 2025 15:28:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/66fd3fb7-0ef9-4e5d-91d0-633a53f3c0f4_1456x1050.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every product can be thought of as a combination of three things: data, functionality, and display.</p><p>The beauty of <a href="https://docs.scalar.com/">API references</a> is that they use a standardized data format (OpenAPI) to provide a massive amount of functionality for you. Building all this functionality would be a ton of work (and maintenance), but because it is similar between products, we can build it for you.</p><p>What&#8217;s not similar or standardized between products is <strong>display</strong>. Design matters a lot. You don&#8217;t want people thinking they&#8217;ve entered another product when they go to your API reference. Ideally, you want them to think you built all the functionality yourself.</p><p>We built <strong>themes</strong> to accomplish exactly this. They aim to make it easy to stylistically integrate our API references with your existing app (without breaking anything). This post covers how they work and how we built them.</p><h2><strong>Enabling smooth integrations through customization</strong></h2><p>The simplest way to customize your API reference is to use a pre-built theme. These include options like <code>moon</code>, <code>solarized</code>, <code>saturn</code>, <code>mars</code>, and more.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Uby5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Uby5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 424w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 848w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 1272w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Uby5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png" width="1456" height="826" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:826,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Web editor themes&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Web editor themes" title="Web editor themes" srcset="https://substackcdn.com/image/fetch/$s_!Uby5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 424w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 848w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 1272w, https://substackcdn.com/image/fetch/$s_!Uby5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc074881c-a279-42ca-9221-460e9029e4bc_2584x1466.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;ve worked hard to make these options you want to use. For example, we&#8217;ve built a custom shadow system to work well in both light and dark modes.</p><p>Normal shadow systems use dark shadows on light backgrounds to create depth, but those same shadows look wrong on dark backgrounds.</p><p>To handle this, we use a subtler shadow with a higher spread in light mode and combine borders with shadows in dark mode. Both use multiple layers for natural depth while also integrating with theme CSS variables.</p><p>If you want to take the customization further, you can then dive into the CSS they are built with. These include <code>light-mode</code> and <code>dark-mode</code> options for details like text color, accents, font, background colors, spacing, and even the sidebar. All this can be edited in the web editor or directly using CSS variables and selectors.</p><p>For example, changing the font requires you to set the <code>withDefaultFonts</code> config option to <code>false</code> and then set it with the <code>--scalar-font</code> variable.</p><pre><code>&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;!-- Link to the font on Google --&gt;
    &lt;link
      href="https://fonts.googleapis.com/css2?family=Roboto"
      rel="stylesheet" /&gt;
    &lt;!-- Overwrite the Scalar font variable --&gt;
    &lt;style&gt;
      :root {
        --scalar-font: 'Roboto', sans-serif;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;!-- Pass the custom configuration object --&gt;
    &lt;script&gt;
      var configuration = {
        theme: 'kepler',
        withDefaultFonts: 'false',
      }
    &lt;/script&gt;
    &lt;script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>All this gives you the options to customize Scalar to your brand and integrate it stylistically.</p><h2><strong>Making customization easy for developers</strong></h2><p>The second part of themes is creating a great developer experience so you actually want to use it. As I hinted in the last section, this is largely done by providing clear variables and organization.</p><p>All variables:</p><ul><li><p>Start with <code>--scalar-</code>.</p></li><li><p>Are grouped logically by function (color, typography, layout)</p></li><li><p>Clearly separated between light and dark mode</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Aji_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Aji_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 424w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 848w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 1272w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Aji_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png" width="1456" height="701" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:701,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Theme variables&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Theme variables" title="Theme variables" srcset="https://substackcdn.com/image/fetch/$s_!Aji_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 424w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 848w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 1272w, https://substackcdn.com/image/fetch/$s_!Aji_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb390de64-711c-47e5-bb77-e9cc773576dc_1940x934.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Beyond this, we also use a layer system to create conflict-free styling. The first layer is <code>scalar-base</code> which contains the core resets that normalize browser default styles and then sets the default styles for Scalar.</p><p>The second layer is <code>scalar-theme</code> which contains theme-specific styles and overrides. These are largely the variables you edit when you want to customize your theme.</p><p>The last piece of theme&#8217;s developer experience is providing tooling support like TypeScript support and a Tailwind preset that provides customer border radius values, border width configurations, color palette mapping, and more.</p><p>Oh, and we know it wouldn&#8217;t be good developer experience without a load of <a href="https://github.com/scalar/scalar/blob/main/documentation/themes.md">documentation</a> and code examples for all things themes. We hope all this makes themes not only easy to customize, but enjoyable too.</p><h2><strong>Making sure themes don&#8217;t break anything</strong></h2><p>The last thing we want Scalar to do is break your app in unexpected ways.</p><p>Similar to the other two sections, this starts by scoping well. For themes, everything is scoped under the <code>scalar-app</code> class using the <code>:where</code> scoping. This is the first layer of defence to prevent unintended style leakage. The <code>scalar-base</code> and <code>scalar-theme</code> layers provide another and the <code>light-mode</code> and <code>dark-mode</code> one after that.</p><p>Another area that is liable to break is scrollbars. Unfortunately, macOS, Windows, and Linux all handle scrollbars differently. The ones on macOS don&#8217;t take up space, but the ones on Windows do. This means the spacing we worked so hard to get right can immediately get messed up.</p><p>To prevent this, we built a detection utility for obtrusive scrollbars that forces them to always be visible. It looks like this:</p><pre><code>export function hasObtrusiveScrollbars(): boolean {
  if (typeof window === 'undefined') return false;

  // Create a 30px square div
  const parent = document.createElement('div')
  parent.setAttribute('style', 'width:30px;height:30px;overflow-y:scroll;')
  parent.classList.add('scrollbar-test')

  // Create a 40px tall child that forces scrolling
  const child = document.createElement('div')
  child.setAttribute('style', 'width:100%;height:40px')
  parent.appendChild(child)
  document.body.appendChild(parent)

  // If the child width is less than 30px, scrollbars are taking up space
  const firstChild = parent.firstChild as HTMLDivElement
  const scrollbarWidth = 30 - firstChild.clientWidth

  document.body.removeChild(parent)

  return !!scrollbarWidth
}</code></pre><p>All of this helps developers gain confidence that Scalar will integrate with their existing setup and not break anything.</p><h2><strong>Themes are a core part of what makes Scalar work</strong></h2><p>Themes are a critical part of making Scalar a drop-in API reference solution that developers love. They enable us to provide all the functionality you&#8217;d want without your users realizing you didn&#8217;t build it.</p><p>Thanks to the customization options, developer tooling, and CSS structure, you can be confident that your API reference will look and feel like a natural extension of your product.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How Cloudinary’s API docs create a great developer experience]]></title><description><![CDATA[And how Scalar helps...]]></description><link>https://blog.scalar.com/p/how-cloudinarys-api-docs-create-a</link><guid isPermaLink="false">https://blog.scalar.com/p/how-cloudinarys-api-docs-create-a</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Mon, 28 Apr 2025 08:02:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/67d8dd43-7abd-42ba-bb19-370978c7e718_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The first line on Cloudinary&#8217;s homepage is &#8220;Image and Video API Platform.&#8221; <strong>API</strong> is front and center as the primary way users interact with their app.</p><p>This API-focus enables Cloudinary to integrate into many more apps and use cases than other image and video tools, but it also means developers expect a world-class API experience.</p><p>A big part of that experience is the docs, tutorials, and videos helping people learn and use the API. Cloudinary shows how much you can do with these tools to make an API accessible. This post goes over their tactics.</p><h2>Giving users multiple ways to get started</h2><p>Every app cares about getting users onboarded and activated, and Cloudinary is no exception.</p><p>Cloudinary&#8217;s range of onboarding materials shows they know people learn differently, especially when it comes to programming. Some want a step-by-step video tutorial to walk them through building a sample app. Others just want straight access to the API. When you are trying to appeal to many types of users, you want to give many options to help them get started.</p><p>The full range of this getting started content which includes:</p><ol><li><p><strong>Quick starts:</strong> Simple tasks that teach the basics of programmatically optimizing, transforming, and managing images and video assets. These are written for both the API and SDKs.</p></li><li><p><strong>Videos:</strong> Interactive video walkthroughs showing the core Cloudinary flows like how to upload assets, optimize images, cropping and resizing, and more.</p></li><li><p><strong>Samples and examples:</strong> Examples with real, modifiable code you can test Cloudinary with. This provides users with an example they can edit to fit their use case.</p></li><li><p><strong>Interactivity:</strong> The ability to test the API directly by sending requests to specific endpoints from the docs. Users can customize their requests, add their actual details, and get responses without needing to download or set up an API client or app.</p></li></ol><p>By providing these options, users can self-select how they want to be taught. This helps them find a method that works for them, and makes it more likely they will succeed onboarding onto Cloudinary.</p><h2>Supporting a diversity of use cases with OpenAPI</h2><p>Although the getting started content is great, as users move beyond basic use cases, they fragment and become complex, but still require support.</p><p>Luckily, being API-first provides the flexibility to handle a diversity of use cases. Developers can integrate Cloudinary into their apps how they want, no pre-built components or complicated implementation patterns needed. This is awesome because it expands the potential market and user base.</p><p>To benefit from all the use cases (and help developers discover new ones), Cloudinary provides a full set of API documentation. They do this by relying on OpenAPI and <a href="https://scalar.com/">Scalar</a>. OpenAPI is a structured standard for describing APIs and Scalar uses it to display an API reference.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wkrM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wkrM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 424w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 848w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 1272w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wkrM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png" width="1456" height="864" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:864,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wkrM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 424w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 848w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 1272w, https://substackcdn.com/image/fetch/$s_!wkrM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53a2a35-d288-4a63-9409-4a2f1d56a7eb_1600x949.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The great part about this combination is that OpenAPI is autogenerated, meaning Cloudinary doesn&#8217;t need to write a bunch of docs every time they add or change an endpoint. Scalar then regenerates the API reference based on the new OpenAPI document.</p><p>This enables users to discover the full range of Cloudinary&#8217;s capability and find the endpoints that fit their more complex and unique use cases.</p><h2>The added benefits of OpenAPI and Scalar</h2><p>Beyond covering many more use cases, the combination of OpenAPI and Scalar provide many benefits for Cloudinary and their users:</p><h3>Functionality</h3><p>An interactive API reference is basically an entire separate app. It requires:</p><ul><li><p>An OpenAPI parser to make the data usable</p></li><li><p>A page generator for each of the endpoints</p></li><li><p>Components with each page to handle URLs, authentication, and examples</p></li><li><p>Templates for each example that need to work with specific client libraries</p></li><li><p>And all this needs to load fast, look nice, and be responsive</p></li></ul><p>If teams had to do this themselves, they might default to a subpar experience: one that rapidly goes out of date, has poor performance, and doesn&#8217;t integrate with their existing website.</p><p>Scalar prevents this by making it as easy as possible to get a modern, stylish, and functional API reference. Cloudinary passes in an OpenAPI file to a script snippet and adds that to their site.</p><p>As a bonus, Cloudinary customizes Scalar using CSS selectors to fit their style and brand. Users likely won&#8217;t notice it's something they haven&#8217;t built themselves.</p><h3>Interactivity</h3><p>If that functionality didn&#8217;t impress you, Scalar built an entire <a href="http://client.scalar.com/">API client</a> into API references. This means users can send requests straight from Cloudinary&#8217;s docs, helping them get started without needing to build a sample app of their own.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;7762c342-3715-4931-96fa-531d54b1b95c&quot;,&quot;duration&quot;:null}"></div><p>Again, adding this sort of interactivity would take a significant amount of work and maintenance, but with Scalar, Cloudinary can provide this sort of best in class user experience easily.</p><h3>Embracing the OpenAPI ecosystem</h3><p>Scalar is just one example of a tool in the wider OpenAPI ecosystem. By standardizing on OpenAPI, Cloudinary (and its users) can benefit from all the progress happening in this ecosystem.</p><p>As an example, another tool in this ecosystem is Speakeasy which enables Cloudinary to generate client SDKs (like Python or Ruby) from an OpenAPI document. These SDKs make setting up and using Cloudinary simpler and gives another option for users with little added work for Cloudinary.</p><p>OpenAPI also helps make developers at Cloudinary more productive by:</p><ul><li><p>Generating code like the SDKs mentioned above</p></li><li><p>Act as a source of truth for API functionality and purpose for internal and external stakeholders</p></li><li><p>Ensure API development best practices like consistent endpoint design and descriptions</p></li></ul><div><hr></div><p>It&#8217;s one thing to say you want to make APIs accessible, but it&#8217;s a lot of work to actually do it.</p><p>Cloudinary does a lot of this themselves, with their high quality docs and getting started guides, but also benefit from what&#8217;s being built in the OpenAPI ecosystem, like Scalar (and we&#8217;re happy to help &#128526;).</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[An introduction to OpenAPI variables]]></title><description><![CDATA[Everything you need to know...]]></description><link>https://blog.scalar.com/p/an-introduction-to-openapi-variables</link><guid isPermaLink="false">https://blog.scalar.com/p/an-introduction-to-openapi-variables</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 23 Apr 2025 16:02:48 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2be3e0af-5246-443c-b9c6-dd50da593bf4_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are two types of OpenAPI variables:</p><ol><li><p><strong>Server variables:</strong> Strings used specifically in the server URL, primarily used for configuration.</p></li><li><p><strong>Parameters:</strong> Potentially complex data types used to define data passed through the API.</p></li></ol><p>This guide providers an overview of defining and using both.</p><h2>Server variables</h2><p>API requests need to go somewhere. That is what the URL defines. Server variables enable easy modification of the URL to support developer experience.</p><p>For example, if you had an app running on a strange port of a customer&#8217;s subdomain, you could define a <code>customerId</code> and <code>port</code> variable like this:</p><pre><code>servers:
  - url: https://{customerId}.coolapp.com:{port}/v2
    variables:
      customerId:
        default: scalar
        description: Customer ID assigned by the service provider
      port:
        enum:
          - '443'
          - '8443'
        default: '443'</code></pre><p>This helps developers on your team know the constraints of the URL as well as easily test multiple configurations of it.</p><p>Generally, server variables fit into one of the following use cases:</p><ul><li><p>Configuring an API to work across dev, staging, and production environments</p></li><li><p>Testing APIs across both <code>http</code> and <code>https</code> protocols</p></li><li><p>Allowing flexibility in port selection</p></li><li><p>Defining subdomains values for customers, regions, or multi-tenant applications.</p></li></ul><h2>Parameters</h2><p>Although not named variables, OpenAPI parameters function as variables in your OpenAPI document. They enable you to define data types and expectations for your API.</p><p>There are four main types of parameters:</p><ul><li><p><strong>Path</strong>, used in URL paths like <code>/users/{id}</code> to identify specific resources.</p></li><li><p><strong>Query</strong>, added after a <code>?</code> in the URL to optionally filter, sort, and paginate.</p></li><li><p><strong>Header</strong>, sent in HTTP for metadata, auth tokens, and tracking.</p></li><li><p><strong>Cookie</strong>, sent in the cookie header for things like session management, but generally avoided/not recommended.</p></li></ul><p>For example, a basic parameter to get a specific user might look like this:</p><pre><code>/users/{id}:
  get:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
          minimum: 5
        description: The ID for the user</code></pre><p>For each parameter, you need to define its <code>name</code>, location (<code>in</code>), and data type (using <code>schema</code> or <code>content</code>). Beyond these, there are several other optional attributes like <code>description</code> and <code>required</code>.</p><p>Parameter attributes have many uses including:</p><ul><li><p><strong>Constraints</strong> including ones like maximum or minimum number values, string length and patterns, array uniqueness, and enum allowed values.</p></li><li><p><strong>Validation</strong> including basic types like <code>integer</code>, data formats like <code>int64</code>, and complex objects (built of many constraints). This enables you to sanitize inputs and protect against attack vectors.</p></li><li><p><strong>Defaults</strong> as well as defining whether null values are allowed with <code>nullable: true</code>.</p></li><li><p><strong>Style and serialization definition</strong> AKA how arrays and objects are formatted in URLs and headers. For example, <code>ids=[1,2,3]</code> changes to:</p><ul><li><p><code>?ids=1,2,3</code> when <code>style: form</code></p></li><li><p><code>?ids=1%202%203</code> when <code>style: spaceDelimited</code></p></li><li><p><code>?ids=1|2|3</code> when <code>style: pipeDelimited</code></p></li></ul></li><li><p><strong>Content types</strong> like <code>application/json</code>, <code>application/xml</code>, <code>application/x-www-form-urlencoded</code>, <code>text/plain</code>, <code>image/png</code>, <code>application/pdf</code>, and their schemas.</p></li></ul><p>For example, to represent a filter that accepts a JSON object with a name and (optionally) age that looks like this <code>/api/endpoint?filter={"name":"John","age":25}</code>, you can define the parameter like this:</p><pre><code>parameters:
  - name: filter
    in: query
    content:
      application/json:
        schema:
          type: object
          required: [name]
          properties:
            name:
              type: string
              minLength: 1
            age:
              type: integer
              minimum: 0
          additionalProperties: false</code></pre><p>Overall, parameters and their attributes provide a flexible way to create consistent and predictable APIs. They make APIs more approachable and understandable, especially when combined with an API reference and client tool like Scalar.</p><h3>Reusing parameters</h3><p>As a bonus, parameters can also be reused so you don&#8217;t need to define them multiple times. This is done by defining them in the components section like this:</p><pre><code>components:
  parameters:
    ApiVersion:
      name: version
      in: header
      required: true
      schema:
        type: string
        enum: [v1, v2]
    PaginationLimit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20</code></pre><p>Once done, you can reference them in your individual paths using <code>$ref</code>:</p><pre><code>paths:
  /users:
    get:
      parameters:
        - $ref: '#/components/parameters/ApiVersion'
        - $ref: '#/components/parameters/PaginationLimit'</code></pre><p>In a way, this is a sort of meta-level variable, a variable inside a variable.</p><h2>The limitation of OpenAPI variables</h2><p>Although OpenAPI provides a lot of flexibility for defining and using variables, it&#8217;s not close to providing everything you could ever want:</p><ul><li><p>Server variables can only be strings and have limited validation options.</p></li><li><p>Parameters don&#8217;t support dynamic default values, computed values, or conditional values.</p></li><li><p>Parameter schemas don&#8217;t support custom validation functions, complex data structures for queries, and other advanced features.</p></li><li><p>Descriptions are limited to basic text limiting rich media, interactivity, and markdown.</p></li></ul><p>Luckily, there&#8217;s no OpenAPI police going around preventing you from extending or changing how your document is written and processed. You can do whatever you want as long as you do the work to make the functionality work as you expect.</p><p>We know because this is exactly what we did when we added environment variables, code samples, internal controls, and more to <a href="https://blog.scalar.com/p/how-we-extended-the-openapi-specification">our version of the OpenAPI specification</a>, which <a href="https://github.com/scalar/scalar/blob/main/documentation/openapi.md">you can read here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[How we extended the OpenAPI specification]]></title><description><![CDATA[Environments, code samples, internal endpoints, and more.]]></description><link>https://blog.scalar.com/p/how-we-extended-the-openapi-specification</link><guid isPermaLink="false">https://blog.scalar.com/p/how-we-extended-the-openapi-specification</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Sun, 06 Apr 2025 17:42:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We love OpenAPI, but as a team that develops a suite of tools built around it, sometimes we can find it limited. Luckily, because we control how the OpenAPI document is processed and displayed, we can do something about this.</p><p>We recently <a href="https://github.com/scalar/scalar/blob/main/documentation/openapi.md">extended the OpenAPI specification</a> with some features we wanted to see. This post is about what we added as well as how we made the changes under the hood to make them work.</p><h2>What we added to the OpenAPI specification</h2><p>At the moment, we have two main tools, our <a href="https://docs.scalar.com/">API reference</a> and <a href="https://client.scalar.com/">client</a>. Our extensions to the OpenAPI specification are to support the experience we want to provide with both of these tools. Specifically, they include:</p><h3>1. Environments</h3><p>Our API client has the concept of environments which enables you to define environment variables and configurations for use across your endpoints. Collections can have multiple different environments.</p><p>You can set up an environment manually in the API client or you can set them up in your OpenAPI doc with <code>x-scalar-environments</code> like this:</p><pre><code># ... rest of your OpenAPI doc

x-scalar-environments:
  production:
    description: 'Production environment'
    color: '#0082D0'
    variables:
      apiKey:
        description: 'Production API Key'
        default: 'prod-key-123'

  development:
    description: 'Development environment'
    color: '#7ED321'
    variables:
      apiKey:
        description: 'Development API Key'
        default: 'dev-key-456'</code></pre><p>This creates a pre-filled environment in the Scalar API client anytime someone imports the OpenAPI doc. This makes environments reusable and much easier to set up.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!36lp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!36lp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 424w, https://substackcdn.com/image/fetch/$s_!36lp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 848w, https://substackcdn.com/image/fetch/$s_!36lp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 1272w, https://substackcdn.com/image/fetch/$s_!36lp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!36lp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png" width="1404" height="360" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a789657d-1f53-4a48-a122-a0345f246b03_1404x360.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:360,&quot;width&quot;:1404,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Scalar environments&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Scalar environments" title="Scalar environments" srcset="https://substackcdn.com/image/fetch/$s_!36lp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 424w, https://substackcdn.com/image/fetch/$s_!36lp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 848w, https://substackcdn.com/image/fetch/$s_!36lp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 1272w, https://substackcdn.com/image/fetch/$s_!36lp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa789657d-1f53-4a48-a122-a0345f246b03_1404x360.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On top of that, you can specify which environment should be the default with <code>x-scalar-active-environment</code>. If you don&#8217;t set this, we just use the first <code>x-scalar-environments</code> value.</p><pre><code># ... rest of your OpenAPI doc

x-scalar-active-environment: development</code></pre><h3>2. Code samples</h3><p>Although we provide a bunch of code samples for everything from cURL to <code>http.client</code> to <code>clj-http</code>, there might be more code samples you want to add to your API reference. This is where <code>x-codeSamples</code> comes in.</p><p>Each of these contains a label, programming language, and source code.</p><pre><code># ... rest of your OpenAPI doc

paths:
  /upload:
    post:
      summary: Upload a file
      description: Uploads a file to the server with optional metadata
      x-codeSamples:
        - lang: Python
          label: Python SDK
          source: |
            import mycompany_sdk

            client = mycompany_sdk.Client("YOUR_API_KEY")

            # Upload file with metadata
            response = client.upload_file(
                file_path="example.pdf",
                metadata={"category": "documents"}
            )
            print(response.file_id)

      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                metadata:
                  type: object
                  properties:
                    category:
                      type: string
      responses:
        '200':
          description: File uploaded successfully</code></pre><p>This then will show up as the sample for the operation in Scalar&#8217;s API reference.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4c7x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4c7x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 424w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 848w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 1272w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4c7x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png" width="1059" height="570" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73674e19-a903-456f-8920-5af5a5972465_1059x570.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:570,&quot;width&quot;:1059,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:73457,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/160392615?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4c7x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 424w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 848w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 1272w, https://substackcdn.com/image/fetch/$s_!4c7x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73674e19-a903-456f-8920-5af5a5972465_1059x570.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>x-codeSamples</code> are great for providing SDK examples, complex operations, language-specific features, and more.</p><h3>3. Tags</h3><p>Tags are used to group similar operations.</p><p>The trouble with tags is that they can have one use internally and a different usage externally. For example, you might have legacy, technical, or abbreviated tags internally, but want a different tag name to show externally.</p><p>To handle this, we added the <code>x-displayName</code> attribute. This overwrites the tag name in the OpenAPI doc with a new one users will see in the API reference. For example, to change the tag name <code>pets_v1</code> to <code>Pets</code>, you can do this:</p><pre><code>tags:
  - name: pets_v1
    x-displayName: Pets # This will display as "Pets" instead of "pets_v1"
    description: Pet management operations</code></pre><p>On top of this, larger OpenAPI docs often have a lot of unorganized tags and endpoints. We provide </p><pre><code>x-tagGroups:
  - name: Store Management
    tags:
      - stores_v1
      - inventory_v1
  - name: Pet Operations
    tags:
      - pets_v1
  - name: Human Resources
    tags:
      - employees_v1</code></pre><h3>4. Internal</h3><p>Because your OpenAPI doc might include endpoints you don&#8217;t want published, we can help hide them from the API reference and client using </p><pre><code>/system/cache/clear:
  post:
    summary: Clear system cache
    description: Internal endpoint to clear the application cache
    x-internal: true
    responses:
      '200':
        description: Cache cleared successfully</code></pre><h3>5. Additional properties</h3><p>Finally, a little quality of life improvement for additional properties. Although normal properties have known fixed names, additional properties do not. If you&#8217;d like to add a name, you can use </p><pre><code>MetadataObject:
  type: object
  properties:
    createdAt:
      type: string
      format: date-time
  additionalProperties:
    x-additionalPropertiesName: metadataField
    type: object
    description: Dynamic metadata fields</code></pre><h2>How we handle these under the hood</h2><p>The work to handle this extended OpenAPI spec and make it actually do something in Scalar&#8217;s API client and reference requires schema validation, integration with our store, and UI implementation.</p><h3>1. Schema validation</h3><p>Each parameter or attribute is handled in a similar way under the hood. They all start with a schema defined using Zod validation. This ensures the OpenAPI doc matches the expected structure, fields are present and have correct types, and optional fields are handled correctly.</p><pre><code>export const xScalarEnvironmentSchema = z.object({
  description: z.string().optional(),
  color: z.string().optional(),
  variables: z.record(z.string(), xScalarEnvVarSchema),
})</code></pre><p>When we change the schema, we don&#8217;t want to break past versions of the schema. To prevent this, we set up and run some migrations. We store the version and types for that version and then the migrator helps change versions. For example, the migration to add <code>x-scalar-environments</code> looked like this:</p><pre><code>export const migrate_v_2_3_0 = (data: v_2_2_0.DataRecord): v_2_3_0.DataRecord =&gt; {
  // Other migration logic...

  const collections = Object.values(data.collections).reduce
    v_2_3_0.DataRecord['collections']
  &gt;((prev, c) =&gt; {
    prev[c.uid] = {
      ...c,
      'x-scalar-environments': c['x-scalar-environments'] || {},
    }
    return prev
  }, {})
  // ... rest of your code</code></pre><h3>2. State management</h3><p>The schema then integrates with the relevant store and its associated mutators. For example, <code>x-scalar-environments</code> integrates with the store responsible for environment handling like this:</p><pre><code>// Handles active state for environments and collections
export const createActiveEntitiesStore = ({
  collections,
  requestExamples,
  requests,
  router,
  workspaces,
}) =&gt; {
  // Environment handling
  const activeEnvironment = computed(() =&gt; {
    if (!activeWorkspace.value.activeEnvironmentId) {
      return {
        uid: '',
        name: 'No Environment',
        value: JSON.stringify(activeWorkspace.value.environments, null, 2),
      }
    }

    // Find environment in collections
    const activeEnvironmentCollection = activeWorkspaceCollections.value.find(
      (c) =&gt; c['x-scalar-environments']?.[activeWorkspace.value.activeEnvironmentId]
    )
    // ...</code></pre><h3>3. UI implementation</h3><p>Finally, with the data validated and integrated with the stores, we can change the UI that users actually see. The only hang up here is that each extension ends up affecting a different part of Scalar: some only impact the API client, others only the API reference.</p><p>On top of this, the complexity varies dramatically:</p><ul><li><p><code>x-displayName</code>, <code>x-tagGroup</code>, <code>x-internal</code>, <code>x-additionalPropertiesName</code> are all relatively straightforward conditional rendering.</p></li><li><p><code>x-codeSamples</code> integrates with the existing code sample code.</p></li><li><p>The most complicated is <code>x-scalar-environments</code> which integrates with multiple different components including all of the ones necessary to create, select, and edit environments as well as variable substitution in requests and more.</p></li></ul><p>In the end, most of what you see is small improvements to the overall experience of using OpenAPI docs with Scalar&#8217;s API client and reference. <strong>This is the point.</strong> We know these small improvements add up to create the best possible experience for API developers.</p><h2>Why extend the OpenAPI specification?</h2><p>Extending the OpenAPI specification enables us to continue to use OpenAPI documents as the source of truth while providing improved developer experience, better configuration management, and improved organization. Expect to see more extensions from us in the future as we build out our tools to work with OpenAPI.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A guide to OpenAPI security (and how we handle it in Scalar)]]></title><description><![CDATA[Keeping your APIs safe, one OpenAPI doc at a time]]></description><link>https://blog.scalar.com/p/a-guide-to-openapi-security-and-how</link><guid isPermaLink="false">https://blog.scalar.com/p/a-guide-to-openapi-security-and-how</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 26 Mar 2025 17:40:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cc1d35f3-6795-422d-8b8a-e189e1e02c79_1014x552.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Just like the real world, there are often areas of your code you want to protect and you set up security to protect them. Unlike the real world, it&#8217;s often not obvious how to interact with these security mechanisms, especially when it comes to APIs.</p><p>This is where defining security in OpenAPI documentation comes in. This enables you to define the authentication and authorization mechanisms you&#8217;ve put into place. This helps the users of your API understand security requirements and interact with protected parts of your API in the right way.</p><p>This post details the types of security, how to define them in your OpenAPI doc, and how they work in Scalar&#8217;s API reference and client.</p><blockquote><p><strong>What about security definitions?</strong> This was the name of the same concept security schemes in OpenAPI 2 (formerly Swagger).</p></blockquote><h2>The many types of security for OpenAPI docs</h2><p>OpenAPI documents are representations of real APIs and those real APIs have many different ways of securing their data. The types of security you can define include:</p><ol><li><p><strong>API keys:</strong> These can be sent in headers, query parameters, or cookies.</p></li><li><p><strong>HTTP:</strong> This includes both basic username and password as well as bearer tokens. You can also define the <code>bearerFormat</code> such as <code>JWT</code>.</p></li><li><p><strong>OAuth 2.0:</strong> Supports multiple types of authorization flows, from standard redirect-based to direct sign-in to application-level. Each flow can define where to request authorization, where to exchange codes for tokens, and available permissions.</p></li><li><p><strong>OpenID Connect:</strong> Finally, OpenID builds on top of OAuth 2.0. It allows clients to automatically get configuration information using a &#8220;well-known&#8221; discovery endpoint.</p></li></ol><p>Each of these can be combined using <code>AND/OR</code> logic. They can also be applied globally or per-operation.</p><h2>How to define OpenAPI security</h2><p>Defining your OpenAPI security starts by adding the <code>securitySchemes</code> property. This defines all the security schemes your API supports. You then use <code>security</code> to apply the schemes globally or per-operation.</p><p>You can think of <code>securitySchemes</code> like the ID types available (passport, driver&#8217;s license, badge) while <code>security</code> specifies which IDs are required to enter.</p><p>For example, if you want to include either <code>BasicAuth</code> or <code>ApiKeyAuth</code> anywhere in your OpenAPI doc, it starts by defining them as <code>securitySchemes</code>:</p><pre><code>components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key</code></pre><p>With <code>securitySchemes</code> defined, you can then apply them at the global level by adding <code>security</code> like this:</p><pre><code>security:
  - ApiKeyAuth: []
  - BasicAuth: []

components:
  securitySchemes:
# ... rest of your OpenAPI doc</code></pre><p>With <code>security</code> set up, you can define your <code>securitySchemes</code>. This can apply to individual operations or globally. For example, to apply <code>BasicAuth</code> globally, you OpenAPI file would look like this:</p><pre><code>security:
  - BasicAuth: []

components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
      description: Basic Authentication using username and password
# ... rest of your OpenAPI file</code></pre><p>To apply it to a single operation, define the <code>security</code> property on the path like we do here:</p><pre><code>components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for protected endpoints

paths:
  /orders:
    post:
      summary: Create new order
      description: Protected endpoint - requires API key
      security:
        - ApiKeyAuth: [] # This operation requires API key
      responses:
        '201':
          description: Order created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  orderId:
                    type: integer
                  status:
                    type: string
        '401':
          description: Unauthorized - invalid or missing API key
  # ... rest of your OpenAPI file</code></pre><p>Each security scheme also has different properties beyond <code>type</code> and the optional <code>description</code> you can define. For example:</p><ul><li><p><code>apiKey</code> has <code>in</code> and <code>name</code></p></li><li><p><code>http</code> has <code>scheme</code> and <code>bearerFormat</code></p></li><li><p><code>oauth2</code> has <code>flows</code> which has <code>authorizationCode</code>, <code>implicit</code>, and <code>password</code> each with their own properties</p></li><li><p><code>OpenIdConnect</code> has <code>openIdConnectUrl</code></p></li></ul><p>Of course, the flip side of all of this is that the OpenAPI doc is just a representation of your actual underlying API. You need to actually implement the security.</p><h3>Defining security with your framework</h3><p>A nice thing about modern API development frameworks is that they generate OpenAPI docs for you. This also means that they provide easy ways to define security for your API. Here&#8217;s an example for <a href="https://github.com/honojs/middleware/tree/main/packages/zod-openapi#how-to-setup-authorization">Hono</a>:</p><pre><code>// Register the security scheme in your OpenAPI docs
app.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', {
  type: 'http',
  scheme: 'bearer',
  description: 'Enter your JWT token in the format: Bearer &lt;token&gt;',
})

// Apply the security to your routes
const route = createRoute({
  method: 'get',
  path: '/protected-resource',
  security: [{ Bearer: [] }],
  handler: async (c) =&gt; {
    const token = c.req.header('Authorization')
    // Token validation logic here
    return c.json({ message: 'Protected data' })
  },
})</code></pre><p>And to add JWT authorization to a .NET API:</p><pre><code>builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =&gt; {
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
        Type = SecuritySchemeType.Http,
        Scheme = "bearer",
        BearerFormat = "JWT",
        Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'"
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement {{
        new OpenApiSecurityScheme {
            Reference = new OpenApiReference {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
            }
        },
        Array.Empty&lt;string&gt;()
    }});
});

// Apply authentication to your endpoints
app.MapGet("/protected", [Authorize] () =&gt; "This endpoint requires authentication")
   .WithOpenApi();</code></pre><p>And finally, for FastAPI:</p><pre><code>from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import secrets

app = FastAPI(
    title="Protected API",
    description="API secured with HTTP Basic Auth"
)

security = HTTPBasic()

def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)):
    # In production, use secure password comparison
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(credentials.password, "secret")

    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

@app.get("/secure-data/",
    summary="Get protected data",
    responses={
        401: {"description": "Invalid credentials"},
        200: {"description": "Successfully authenticated"}
    })
def secure_data(username: str = Depends(verify_credentials)):
    return {"message": f"Hello, {username}"}</code></pre><p>This is where it really pays off to have a framework that generates the OpenAPI doc (well) for you. For example, Django doesn&#8217;t generate an OpenAPI doc automatically, making interacting with it for API reference or debugging a lot harder.</p><h2>How Scalar handles security</h2><p>The whole point of defining a security scheme is that it makes using an API easier. This means API references and clients need to handle the many security schemes OpenAPI provides.</p><h3>API reference</h3><p>Our <a href="https://galaxy.scalar.com/">API reference</a> supports API key, HTTP, and OAuth 2.0 security schemes. Based on your OpenAPI document and the security schemes applied to your operations, users can choose one of these and send requests with it.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;fd379a06-7381-4ae5-967e-e982047127e0&quot;,&quot;duration&quot;:null}"></div><p>Behind the scenes, we handle the logic for applying the correct scheme for each operation, include the UI to gather the needed information, set up the correct auth method on users requests, handle state for the auth, and handle errors that come from invalid credentials or other auth failures.</p><h3>API client</h3><p>The <a href="https://client.scalar.com/">API client</a> looks very similar. The big difference is that because your client state can be private to you, we can store it between sessions and collections. This also means you might have multiple collections as well as multiple active security schemes.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;e7d8dcce-6aa1-4ce2-aa0c-dc9deb244afb&quot;,&quot;duration&quot;:null}"></div><p>To handle this, we store auth values at the collection-level, support operation-level overrides, and allow you to select the scheme on want on per-request. The selectors for schemes are also more interactive, allowing editing of API key name fields and scheme deletion.</p><p>Our API client also pre-fills as much information as possible. It auto detects security schemes, creates default value structures based on scheme types, pre-configs OAuth flows if provided, and more.</p><h2>Supporting API security and developer experience</h2><p>As with many areas of Scalar, we continue to improve the connection between your OpenAPI doc, API reference, and API client. For example, we recently added the ability to sync authentication settings between the API reference and the API client. The API reference can pass auth settings to the client modal and the select security schemes become the default in the client.</p><p>Security shouldn&#8217;t come at the cost of developer experience. The tools Scalar provides helps developers authenticate quickly and correctly. This helps them test endpoints, debug, and ultimately build better (and more secure) APIs.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[The hidden complexity of building drag and drop]]></title><description><![CDATA[What we learned building our own slick drag and drop package at Scalar]]></description><link>https://blog.scalar.com/p/the-hidden-complexity-of-building</link><guid isPermaLink="false">https://blog.scalar.com/p/the-hidden-complexity-of-building</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 19 Mar 2025 17:53:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/be2639da-760a-4794-91bf-0e9ee332f02f_2400x1340.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When we rearrange things in the real world, we pick them up and put them down. Picking something up, moving it, and putting it down is the pattern we unconsciously rely on.</p><p>Users expect that same pattern to work in the apps they use. Unfortunately for those who have to implement this, gravity isn&#8217;t built into computers. Writing all the logic needed to drag and drop in an app is a lot of work.</p><p>We know because we recently had to go through all this work when building drag and drop into our API client. This post shows off what we built and all the work under the surface to make it happen.</p><h2>What can you drag and drop in Scalar&#8217;s API client?</h2><p>Most obviously, you can move and rearrange the three main objects in our API client: collections, folders, and requests. The last two can be dragged and dropped between folders, into folders, and out of folders.</p><p>On top of this, drag and drop can be used to import OpenAPI docs. This can be done both by dragging in <code>.yml</code>, <code>.yaml</code>, and <code>.json</code> files as well as URLs.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;577283e3-721d-4c02-b5cf-d21793fe80a4&quot;,&quot;duration&quot;:null}"></div><h2>What&#8217;s so tricky about drag and drop?</h2><p>Creating a slick drag and drop experience required us to solve the following challenges:</p><ul><li><p><strong>Where.</strong> You can&#8217;t drag everything everywhere. Only certain objects can be dropped into certain spots. This means you need validation and visual feedback like drop zones, UI updates, cancellation, and error handling.</p></li><li><p><strong>State management</strong>. Both dragging and dropping needs to work with the many levels of state the API client has including collections, folders, and requests as well as their parent-child relationships. The UI aspects like collapsed versus expanded also needs to be handled.</p></li><li><p><strong>Performance.</strong> When you drag an element, your browser fires <code>dragover</code> events many times per second. If you don&#8217;t throttle these, you run potentially expensive calculations like determining drop zones, calculating positions, updating UI indicators on every single mouse movement. This can create UI jitters or lag your entire app.</p></li><li><p><strong>Context.</strong> Requests and collections aren&#8217;t simple. They include information on authentication settings, examples, test cases, response history, security schemes, and servers. Behind this is an structured OpenAPI document that needs to stay valid.</p></li></ul><h2>How does our API client handle drag and drop challenges?</h2><p>Rather than existing libraries like <code>react-dnd</code>, <code>vue-draggable</code>, we developed our own <a href="https://github.com/scalar/scalar/tree/main/packages/draggable">@scalar/draggable package</a> with a <a href="https://github.com/scalar/scalar/blob/main/packages/draggable/src/Draggable.vue">Draggable.vue component</a>. To make dragging and dropping consistent across the API client, the component uses standard HTML 5 drag and drop APIs to:</p><ul><li><p>Track dragging and hovering state</p></li><li><p>Handle drag events</p></li><li><p>Provide visual feedback</p></li><li><p>Customize drop zones</p></li><li><p>Track hierarchy for validation</p></li></ul><p>Here&#8217;s how we specifically handle some of the challenges with dragging and dropping we identified:</p><h3>Validation</h3><p>The <code>Draggable.vue</code> component enables us to define droppable checks unique to each component and object type. For example, requests and folders can&#8217;t be dropped in lower-level containers, they can&#8217;t receive drops, and they can&#8217;t be read only:</p><pre><code>// RequestSidebarItem.vue
const _isDroppable = (draggingItem, hoveredItem) =&gt; {
  if (activeWorkspace.value.isReadOnly) return false
  if (requestExamples[hoveredItem.id]) return false
  if (collections[draggingItem.id]) return false // Collections can't be dropped anywhere
  return true
}</code></pre><p>Collections can be reordered but not dropped in &#8220;Drafts&#8221;, can receive requests and folders as dropped but only as children, and can&#8217;t be read only:</p><pre><code>// Request.vue (for collections)
const _isDroppable = (draggingItem, hoveredItem) =&gt; {
  if (activeWorkspace.value.isReadOnly) return false
  if (!collections[draggingItem.id] &amp;&amp; hoveredItem.offset !== 2) return false // Requests/folders must be dropped AS CHILDREN
  if (
    collections[draggingItem.id] &amp;&amp;
    collections[hoveredItem.id]?.spec?.info?.title === 'Drafts'
  )
    return false
  return true
}</code></pre><p>The <code>Draggable.vue</code> component also has hierarchy validation to ensure an object isn&#8217;t highlighted if hovering over itself or child. It also has configurable position-based validation.</p><pre><code>const getDraggableOffsets = computed(() =&gt; {
  let ceiling = 0.5
  let floor = 0.5

  // If hovered over is collection &amp;&amp; dragging is not a collection
  if (!collections[draggingItem?.id] &amp;&amp; isCollection.value) {
    ceiling = 1
    floor = 0
  }
  // Has children but is not a request or a collection
  else if (hasChildren.value &amp;&amp; !isRequest.value &amp;&amp; !isCollection.value) {
    ceiling = 0.8
    floor = 0.2
  }

  return { ceiling, floor }
})</code></pre><p>All of this is implemented through props passed down to the draggable component. This ensures that the <code>Draggable</code> component is scoped to only the drag and drop logic it needs to handle and makes it more reusable.</p><h3>Visual feedback</h3><p>We provide feedback at 3 spots for dragging and dropping:</p><ol><li><p><strong>Above.</strong> Uses the <code>dragover-above</code> class to show feedback when hovering above an item.</p></li><li><p><strong>Below.</strong> Uses the <code>dragover-below</code> class to show feedback when hovering below an item.</p></li><li><p><strong>As a child.</strong> Uses the <code>dragover-asChild</code> class when hovering to nest inside an item.</p></li></ol><p>Both above and below a 3px blue semi-transparent line either above or below the item. For nesting, we highlight the entire target with a semi-transparent blue overlay.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a9ee5c17-e962-4e29-bf69-157477980491&quot;,&quot;duration&quot;:null}"></div><p>Beyond this, there are a few styling things we do to make this look good. The styles are implemented using CSS pseudo-elements (<code>:after</code>), we use <code>pointer-events: none</code> to ensure indicators don&#8217;t interfere with drag operations, and use CSS <code>color-mix()</code> to create a subtle, semi-transparent effect that matches the UI theme.</p><h3>Performance</h3><p>Because the <code>dragover</code> event fires extremely frequently and is tied to position and visual calculations, it can cause UI jitter or lag. To prevent this, we built a throttle function that:</p><ol><li><p>Limits the <code>dragover</code> handler to firing at most once every 25 milliseconds (40 times per second)</p></li><li><p>Drops intermediate events during that 25ms window</p></li><li><p>Still ensures enough updates to maintain smooth visual feedback</p></li></ol><p>This function looks like this:</p><pre><code>export const throttle = &lt;T extends (...args: any[]) =&gt; void&gt;(
  func: T,
  limit: number,
): T =&gt; {
  let inThrottle: boolean
  let lastResult: any

  return ((...args) =&gt; {
    if (!inThrottle) {
      inThrottle = true

      lastResult = func(...args)

      setTimeout(() =&gt; {
        inThrottle = false
      }, limit)
    }

    return lastResult
  }) as T
}</code></pre><p>It takes a function and a time limit, returns a wrapped version that only executes once within the time window, and tracks whether we&#8217;re still in the throttle.</p><p>This is enough to provide smooth (40 fps) animations while still significantly reducing the processing overhead of raw <code>dragover</code> events.</p><h3>State management</h3><p>The core <code>Draggable.vue</code> component doesn&#8217;t handle state, only hover positions as well as drag start and end events with that position information.</p><p>The actual state management is lifted to the parent component <code>Request.vue</code>. This prevents drops in read-only mode, handles collection-specific rules, and manages the hierarchical relationship between collections, folders, and requests. It also handles different mutation types depending on the target.</p><pre><code>const mutate = (uid: string, childUids: string[]) =&gt; {
  if (collections[uid]) collectionMutators.edit(uid, 'childUids', childUids)
  else if (folders[uid]) folderMutators.edit(uid, 'childUids', childUids)
}</code></pre><p>This structure isolates drag and drop from our business logic, ensures we handle OpenAPI document validity constraints, and provides us the flexibility to add new drag and drop rules without modifying the base component.</p><h2>The undersurface complexity of drag and drop</h2><p>Although drag and drop seems simple on the surface, there is a lot of complexity to make it work nicely. It required us to handle OpenAPI document validation, performance, visual feedback, hierarchy, and state management.</p><p>This is representative of one of many of the little optimizations we do to try to make working with APIs as easy as possible.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading our blog! Subscribe for free to receive more posts like this one :)</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How we sped up our API docs 25x]]></title><description><![CDATA[Finding and fixing our shockingly slow sidebar]]></description><link>https://blog.scalar.com/p/how-we-sped-up-our-api-docs-25x</link><guid isPermaLink="false">https://blog.scalar.com/p/how-we-sped-up-our-api-docs-25x</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 12 Mar 2025 18:03:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!1gF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>API docs have a unique structure compared to other types of documentation. They have many related pages which need to be easily accessible from each other. This can mean a lot of menu items.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1gF2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1gF2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 424w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 848w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 1272w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1gF2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png" width="1456" height="709" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:709,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:226809,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/158263611?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1gF2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 424w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 848w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 1272w, https://substackcdn.com/image/fetch/$s_!1gF2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24300eff-3adc-41cd-aa69-d99e8ca9a883_1863x907.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Our users pushed this to the limit, and it was showing. They were uploading 7+ MB OpenAPI documents, some with 30+ groups, 10+ endpoints per group, and 50+ models. This was causing significant performance issues, something we know developers (the users of an API) are particularly sensitive about.</p><h2>Diagnosing our performance issues</h2><p>The obvious first spot to look at is how data is imported into the client. </p><p>To check this, Amrit sprinkled <code>console.time</code> and <code>console.timeEnd</code> calls around the relevant methods. (Un)fortunately, they were fine, and he moved on to checking for issues after the data was imported and set up.</p><p>Next, he experimented by commenting out chunks of the UI until he saw load times dramatically decrease. This is how he discovered the culprit component: the <a href="https://github.com/scalar/scalar/pull/3175/files#diff-6221b31824b2fb1816c1780e614fe3dc504ab9cc7ff6b8fcc305ac813cee8bd3">Request Sidebar</a>.</p><p>By timing the <code>onBeforeMount</code> and <code>onMounted</code> hooks, he found that a 7 MB OpenAPI spec took 2.6 seconds to mount (way too long). Narrowing down, it was specifically the <a href="https://github.com/scalar/scalar/pull/3175/files#diff-3179b1678c7f778417b849d87878807ad518233ca909f20fa108f7fe4e8784ef">Request Sidebar Item</a> that was causing the issue.</p><p>The each instance of <code>RequestSidebarItem</code> created multiple child modals and menus. These made it easy for editors to rename and delete items, but, when repeated across hundreds of sidebar items, were causing major slowdowns.</p><h2>Refactoring our sidebar to fix the performance issues</h2><p>Fixing this required us to rework how users edit sidebar items.</p><p>To start, Amrit hoisted the edit and rename modals outside of <code>RequestSidebarItem</code> to ensure they were only created once. He also decided that the context menu was doing more harm than good. It enabled users to right-click to edit but didn't add functionality beyond what was already there, so he removed it.</p><p>The trickiest fix was the dropdown menu. <code>headlessui</code>, a component library we use, makes it harder to open the menu when the triggering button wasn&#8217;t a child of the component. This is why the menu was repeated inside every instance of <code>RequestSidebarItem</code>; we needed the <code>...</code> button to open it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PdzZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PdzZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 424w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 848w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 1272w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PdzZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png" width="1456" height="519" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:519,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111681,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.scalar.com/i/158263611?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PdzZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 424w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 848w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 1272w, https://substackcdn.com/image/fetch/$s_!PdzZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7375a878-b17b-46eb-bfd1-df44d4a8e648_1892x675.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Opening this menu in the right spot required:</p><ol><li><p>Passing a <code>targetRef</code> to both the floating popup and dropdown menu.</p></li><li><p>Targeting the menu on the parent element because the button to open the menu only appeared on hover. This was resetting the position of the floating menu.</p></li><li><p>Manually focusing on the dropdown menu once opened and setting up a global click listener to close it.</p></li></ol><h2>Maintaining functionality while improving performance</h2><p>Most users probably didn't notice changes to the sidebar functionality. It looks the same and works the same.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;dac7c709-8367-4c71-849d-a8cae8d258b3&quot;,&quot;duration&quot;:null}"></div><p>What users with large API docs will notice is a dramatic performance improvement. Thanks to these fixes, loading times for a 7 MB OpenAPI doc went improved 25x, going from 2.6s to 0.11s.</p><p>Improving performance is a continual process. For example, immediately after this, we upgraded to Vue 3.5, which came with <a href="https://blog.vuejs.org/posts/vue-3-5#reactivity-system-optimizations">reactivity system optimizations</a>. As a company building for developers, we know how important performance is, so expect these improvements to continue.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[How .NET 9 and Scalar solve the problem of under-documented APIs]]></title><description><![CDATA[.NET 9 &#129309; Scalar]]></description><link>https://blog.scalar.com/p/how-net-9-and-scalar-solve-the-problem</link><guid isPermaLink="false">https://blog.scalar.com/p/how-net-9-and-scalar-solve-the-problem</guid><dc:creator><![CDATA[Ian]]></dc:creator><pubDate>Wed, 05 Mar 2025 15:32:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Stop me if you&#8217;ve heard this one before:</p><ol><li><p>You finish and ship a change to your API.</p></li><li><p>You don&#8217;t bother to document it.</p></li><li><p>You forget to generate and/or update the API docs.</p></li><li><p>You definitely don&#8217;t publish the newly updated API docs so others can actually use it.</p></li></ol><p>This under-documentation means fewer people being able to use the API, more confusion when you return to this code, and makes it trickier to debug if issues occur.</p><p>We don&#8217;t blame you for under-documenting your API. In many codebases, the documentation is disconnected from the actual code. This creates needless work in the process of documenting, updating, and publishing API docs.</p><p>With the release of .NET 9 in combination with Scalar, we are changing this.</p><h2>What&#8217;s changed with the .NET 9 release?</h2><p>The `ASP.NET` ecosystem used to have a solution for working with APIs documents in Swashbuckle. This generated API documentation and a UI for your ASP.NET Core API and was a solution to the under-documented problem.</p><p>The big problem is that Swashbuckle has not kept up with .NET releases. There was not an official release for .NET 8 and has otherwise seemingly been abandoned. No PRs have been merged since November 2022.</p><p>To fix this gap, the ASP.NET Core team is <a href="https://github.com/dotnet/aspnetcore/issues/54599">removing Swashbuckle</a> with the release of .NET 9 and replacing it with their own solution for OpenAPI document generation.</p><p>What this looks like is built-in support for OpenAPI document generation via the <code>Microsoft.AspNetCore.OpenApi</code> and <code>Microsoft.Extensions.ApiDescription.Server</code> packages. This <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&amp;tabs=visual-studio%2Cminimal-apis#using-scalar-for-interactive-api-documentation">generates</a> a JSON OpenAPI document automatically based on a map of the API.</p><p>For example, after installing both packages, add <code>builder.Services.AddOpenApi();</code> and <code>app.MapOpenApi();</code> to your <code>Program.cs</code> file:</p><pre><code>var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

app.MapGet("/", () =&gt; "Hello world!");

app.Run();</code></pre><p>Running this then generates an OpenAPI document at <code>https://localhost:&lt;port&gt;/openapi/v1.json</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CkN3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CkN3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 424w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 848w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 1272w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CkN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png" width="1456" height="1171" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1171,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Dotnet 9 OpenAPI&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Dotnet 9 OpenAPI" title="Dotnet 9 OpenAPI" srcset="https://substackcdn.com/image/fetch/$s_!CkN3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 424w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 848w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 1272w, https://substackcdn.com/image/fetch/$s_!CkN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f04e6c4-42a9-48a7-82e8-9fb654dbe118_1480x1190.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Generating this is document is a great first step, but is only part of the solution. It then needs to be turned into documentation people can use. This is where Scalar&#8217;s suite of API tools come in.</p><h2>Setting up Scalar&#8217;s ASP.NET package</h2><p>Thanks to <a href="https://github.com/captainsafia">@captainsafia</a> and <a href="https://github.com/xC0dex">@xC0dex</a> of our community, Scalar has a ASP.NET API package.</p><p>To set it up, simply install the package:</p><pre><code>dotnet add package Scalar.AspNetCore</code></pre><p>And add the directive and <code>MapScalarApiReference()</code> to your program:</p><pre><code>using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();
app.MapScalarApiReference();

app.MapGet("/", () =&gt; "Hello world!");

app.Run();</code></pre><p>Like magic, this gives you a full set of API docs when you navigate to <code>https://localhost:&lt;port&gt;/scalar/v1</code> in your browser.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kaOL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kaOL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 424w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 848w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 1272w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kaOL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png" width="1456" height="829" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:829,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Dotnet 9 API docs&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Dotnet 9 API docs" title="Dotnet 9 API docs" srcset="https://substackcdn.com/image/fetch/$s_!kaOL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 424w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 848w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 1272w, https://substackcdn.com/image/fetch/$s_!kaOL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad9851fe-7441-49bc-9a95-a3022904481e_2842x1618.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Under the hood of how Scalar generates your API docs</h2><p>You might be asking &#8220;how did that happen so quickly?&#8221; Well, when you add and run Scalar in your ASP.NET API, we do the following:</p><ol><li><p>Configure Scalar options including the location of your OpenAPI document and any customizations you want like titles, themes, and authentication</p></li><li><p>Map a GET endpoint to your API that serves a page.</p></li><li><p>On that page, use your OpenAPI document and Scalar configuration to load your version of Scalar&#8217;s API docs from a CDN.</p></li></ol><p>This creates a complete set of API docs in a simple to use UI without all of the work of building it. For further customization, <a href="https://docs.scalar.com/register">you can create an account</a> and edit your docs however you like.</p><h2>Solving the under-documented API problem</h2><p>The combination of new ASP.NET OpenAPI generation and Scalar solves the undocumented API problem. No more needing to document, generate, update, and serve your API docs.</p><p>We don&#8217;t stop there either. Our API seamlessly turn into a fully featured API client. This means you not only get to document your API but test it. This is a core part of our aim of creating a world-class developer experience for your APIs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nm9-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nm9-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 424w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 848w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nm9-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png" width="1456" height="824" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:824,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Dotnet 9 API client&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Dotnet 9 API client" title="Dotnet 9 API client" srcset="https://substackcdn.com/image/fetch/$s_!nm9-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 424w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 848w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 1272w, https://substackcdn.com/image/fetch/$s_!nm9-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7cf53bcc-3c00-48ad-a283-4e5202aa06db_2868x1624.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.scalar.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts weekly.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Scalar Joins the Open-Source Pledge]]></title><description><![CDATA[Sep 1st, 2024]]></description><link>https://blog.scalar.com/p/scalar-joins-the-open-source-pledge</link><guid isPermaLink="false">https://blog.scalar.com/p/scalar-joins-the-open-source-pledge</guid><dc:creator><![CDATA[Marc]]></dc:creator><pubDate>Fri, 28 Feb 2025 05:23:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xWwU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When we met <a href="https://x.com/chadwhitacre_">Chad Whitacre </a>at the Open-Source Summit in Seattle earlier this year, we instantly bonded over all things open-source! He also told me the very early stages of the Open-Source Pledge that <a href="https://sentry.io/">Sentry </a>was starting and I couldn't have been more excited about joining it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://x.com/MarcLaventure/status/1782403644387922189/" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xWwU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 424w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 848w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 1272w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xWwU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png" width="1212" height="1376" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1376,&quot;width&quot;:1212,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2063480,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:&quot;https://x.com/MarcLaventure/status/1782403644387922189/&quot;,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://marclave.substack.com/i/158084021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xWwU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 424w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 848w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 1272w, https://substackcdn.com/image/fetch/$s_!xWwU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7b27b585-37f0-4c2f-ac27-1e0ed1eaea29_1212x1376.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What is OSS Pledge?</h2><p>You can read more about it <a href="https://osspledge.com/">here </a>, but in short, it's paying Open Source maintainers. The minimum to participate is $2,000 per full-time employed developer per year.</p><h2>How much Scalar Gives</h2><p>We have donated $25,569.34 USD so far in 2024, which equates to $3,652.76 USD per Scalar Employee. Here is a list of who we donate to on a monthly basis and single donations for open-source foundations:</p><ul><li><p>litestar-org $250/month <a href="https://litestar.dev/">(LiteStar)</a></p></li><li><p>SaltyAom $300/month <a href="https://elysiajs.com/">(ElysiaJS)</a></p></li><li><p>colinhacks $99/month <a href="https://zod.dev/">(Zod)</a></p></li><li><p>tiangolo $500/month <a href="https://fastapi.tiangolo.com/">(FastAPI)</a></p></li><li><p>yusukebe $100/month <a href="https://hono.dev/">(Hono)</a></p></li><li><p>marijnh $5/month <a href="https://github.com/marijnh">(CodeMirror/ProseMirror)</a></p></li><li><p>daveshanley $100/month <a href="https://github.com/pb33f/libopenapi">(libopenapi)</a></p></li><li><p>LinusBorg $5/month <a href="https://github.com/LinusBorg">(Vue.js Maintainer)</a></p></li><li><p>pi0 $31/month <a href="https://github.com/pi0">(Nuxt/UnJS)</a></p></li><li><p>dmonad $10/month <a href="https://github.com/dmonad">(YJS)</a></p></li><li><p>thibaultleouay $10/month <a href="https://www.openstatus.dev/">(OpenStatus)</a></p></li><li><p>fuma-nama $10/month <a href="https://github.com/fuma-nama">(FumaDocs)</a></p></li><li><p>litestar + python (3,529.34)</p></li><li><p>algoria OSS Bounty TypeScript Challenge + Daniel Roe (5,000)</p></li></ul><h2>Thank You OSS</h2><p>Most of the team at Scalar have started their Engineering careers because of Open-Source, and we couldn't be more honoured to be a part of the OSS Pledge! Thanks to all the maintainers and contributors whose software we are priviliged to use and build with.</p><p>Marc CEO &amp; co-founder, Scalar</p><p></p>]]></content:encoded></item></channel></rss>