<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Scalar Blog</title>
    <link>https://scalar.com/blog</link>
    <description>Engineering stories and news from the Scalar team</description>
    <atom:link href="https://scalar.com/blog/rss.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Tue, 13 Oct 2026 00:00:00 GMT</lastBuildDate>
    <item>
      <title>OpenAPI with NestJS</title>
      <link>https://scalar.com/blog/posts/2026-10-13-openapi-with-nestjs</link>
      <guid>https://scalar.com/blog/posts/2026-10-13-openapi-with-nestjs</guid>
      <pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;NestJS generates an OpenAPI document from your controllers and DTOs through the official &lt;code&gt;@nestjs/swagger&lt;/code&gt; package. Add it, build a document in &lt;code&gt;main.ts&lt;/code&gt;, and the JSON is served next to your API. Like most code-first generators, the setup takes five minutes and the accuracy takes longer, because TypeScript types disappear at runtime and Nest can only document what it can see.&lt;/p&gt;
&lt;p&gt;This is part one of a three-part series, and it is vendor-neutral. Everything here is standard NestJS and works the same whichever documentation UI, SDK generator or linter reads the result. Part two covers what you can build from the document, and part three walks through one concrete setup.&lt;/p&gt;
&lt;p&gt;The package is called &lt;code&gt;@nestjs/swagger&lt;/code&gt; for historical reasons. What it produces is an OpenAPI document; &quot;Swagger&quot; survives in the name and in the bundled Swagger UI. Behaviour below matches the &lt;a href=&quot;https://docs.nestjs.com/openapi/introduction&quot;&gt;NestJS OpenAPI documentation&lt;/a&gt; and the &lt;a href=&quot;https://github.com/nestjs/swagger&quot;&gt;&lt;code&gt;nestjs/swagger&lt;/code&gt; source&lt;/a&gt; as of September 2026.&lt;/p&gt;
&lt;h2&gt;Install and bootstrap&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;shell&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;npm install --save @nestjs/swagger&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use the Fastify adapter, also install &lt;code&gt;@fastify/static&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then build the document in &lt;code&gt;main.ts&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { NestFactory } from &apos;@nestjs/core&apos;
import { DocumentBuilder, SwaggerModule } from &apos;@nestjs/swagger&apos;
import { AppModule } from &apos;./app.module&apos;

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  const config = new DocumentBuilder()
    .setTitle(&apos;Bookstore API&apos;)
    .setDescription(&apos;Books, authors and orders.&apos;)
    .setVersion(&apos;1.0.0&apos;)
    .addTag(&apos;books&apos;)
    .build()

  const documentFactory = () =&gt; SwaggerModule.createDocument(app, config)
  SwaggerModule.setup(&apos;api&apos;, app, documentFactory)

  await app.listen(3000)
}
bootstrap()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Passing a factory instead of a finished document is the current recommended form: Nest builds the document on the first request, not during startup.&lt;/p&gt;
&lt;p&gt;With that in place:&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Swagger UI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api-json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The OpenAPI document as JSON&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api-yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The same document as YAML&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;All three are configurable through the fourth argument to &lt;code&gt;setup()&lt;/code&gt;, which takes options such as &lt;code&gt;jsonDocumentUrl&lt;/code&gt;, &lt;code&gt;yamlDocumentUrl&lt;/code&gt;, &lt;code&gt;ui&lt;/code&gt; (set it to &lt;code&gt;false&lt;/code&gt; to serve only the document) and &lt;code&gt;raw&lt;/code&gt; (choose which formats are exposed). &lt;code&gt;useGlobalPrefix: true&lt;/code&gt; puts them under your global prefix.&lt;/p&gt;
&lt;h2&gt;Which OpenAPI version you get&lt;/h2&gt;
&lt;p&gt;The document declares &lt;code&gt;openapi: 3.0.0&lt;/code&gt; by default; that is the base document in the &lt;a href=&quot;https://github.com/nestjs/swagger/blob/master/lib/fixtures/document.base.ts&quot;&gt;package source&lt;/a&gt;. &lt;code&gt;DocumentBuilder.setOpenAPIVersion(&apos;3.1.0&apos;)&lt;/code&gt; changes the declared version. Changing the number does not by itself rewrite schemas into 3.1 style, so if you declare a newer version, run the output through a validator for that version before you rely on it. Recent releases of the package have started to add newer features, such as hierarchical tags through &lt;code&gt;addTag()&lt;/code&gt;; check the &lt;a href=&quot;https://docs.nestjs.com/openapi/operations&quot;&gt;operations page&lt;/a&gt; for what your installed version supports.&lt;/p&gt;
&lt;p&gt;For most teams, a correct 3.0 document is a fine starting point. Every documentation UI and SDK generator you are likely to use reads it. Our &lt;a href=&quot;https://scalar.com/learn/openapi/openapi-3-1-vs-3-0&quot;&gt;OpenAPI 3.1 vs 3.0&lt;/a&gt; guide explains what you gain by moving.&lt;/p&gt;
&lt;h2&gt;Why DTOs need help&lt;/h2&gt;
&lt;p&gt;Here is the core problem. This controller looks fully typed:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;@Controller(&apos;books&apos;)
export class BooksController {
  @Get(&apos;:id&apos;)
  findOne(@Param(&apos;id&apos;) id: string): Promise&amp;#x3C;Book&gt; {
    return this.booksService.findOne(id)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But TypeScript interfaces and type annotations are erased at compile time. At runtime, Nest can see that there is a &lt;code&gt;GET /books/:id&lt;/code&gt; route with an &lt;code&gt;id&lt;/code&gt; path parameter, and it can read the class metadata TypeScript emits for decorated members. It cannot see that the method returns a &lt;code&gt;Book&lt;/code&gt;, and it cannot see which properties &lt;code&gt;Book&lt;/code&gt; has unless something records them.&lt;/p&gt;
&lt;p&gt;There are two ways to record them: decorators you write by hand, or the CLI plugin that writes them for you at compile time. You will likely use both.&lt;/p&gt;
&lt;h3&gt;Decorators by hand&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { ApiProperty, ApiPropertyOptional } from &apos;@nestjs/swagger&apos;

export class Book {
  @ApiProperty({ description: &apos;Unique identifier&apos;, example: &apos;bk_123&apos; })
  id: string

  @ApiProperty({ maxLength: 200, example: &apos;Dune&apos; })
  title: string

  @ApiPropertyOptional({ example: &apos;Book one&apos; })
  subtitle?: string
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DTOs must be classes, not interfaces or type aliases, for any of this to work.&lt;/p&gt;
&lt;h3&gt;The CLI plugin&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.nestjs.com/openapi/cli-plugin&quot;&gt;Swagger CLI plugin&lt;/a&gt; is a TypeScript transformer that reads your types during compilation and adds the metadata for you. Enable it in &lt;code&gt;nest-cli.json&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;json&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;compilerOptions&quot;: {
    &quot;plugins&quot;: [
      {
        &quot;name&quot;: &quot;@nestjs/swagger&quot;,
        &quot;options&quot;: {
          &quot;classValidatorShim&quot;: true,
          &quot;introspectComments&quot;: true
        }
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the plugin on, every property in a file ending in &lt;code&gt;.dto.ts&lt;/code&gt; or &lt;code&gt;.entity.ts&lt;/code&gt; is documented as if it had &lt;code&gt;@ApiProperty()&lt;/code&gt;, including whether it is optional, its type, and its default value. &lt;code&gt;classValidatorShim&lt;/code&gt; turns &lt;code&gt;class-validator&lt;/code&gt; decorators such as &lt;code&gt;@MaxLength(200)&lt;/code&gt; into schema constraints. &lt;code&gt;introspectComments&lt;/code&gt; turns JSDoc comments on properties into descriptions and examples, so you do not write every description twice.&lt;/p&gt;
&lt;p&gt;Two things trip people up:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;File naming.&lt;/strong&gt; The plugin only processes files matching &lt;code&gt;dtoFileNameSuffix&lt;/code&gt; (default &lt;code&gt;[&apos;.dto.ts&apos;, &apos;.entity.ts&apos;]&lt;/code&gt;) and &lt;code&gt;controllerFileNameSuffix&lt;/code&gt; (default &lt;code&gt;[&apos;.controller.ts&apos;]&lt;/code&gt;). A DTO in &lt;code&gt;book.ts&lt;/code&gt; is silently skipped. Rename it or change the option.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other builders.&lt;/strong&gt; The plugin runs as part of the Nest CLI&apos;s TypeScript compilation. With SWC, the docs recommend &lt;code&gt;nest start -b swc --type-check&lt;/code&gt;, and monorepos need a metadata generation step plus &lt;code&gt;SwaggerModule.loadPluginMetadata()&lt;/code&gt;. With other build tools, check the plugin page before assuming it ran.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Describe responses explicitly&lt;/h2&gt;
&lt;p&gt;Even with the plugin, Nest does not know what a handler returns. Say so:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import {
  ApiNotFoundResponse,
  ApiOkResponse,
  ApiOperation,
  ApiTags,
} from &apos;@nestjs/swagger&apos;

@ApiTags(&apos;books&apos;)
@Controller(&apos;books&apos;)
export class BooksController {
  @Get(&apos;:id&apos;)
  @ApiOperation({ summary: &apos;Get a book&apos; })
  @ApiOkResponse({ type: Book, description: &apos;The requested book&apos; })
  @ApiNotFoundResponse({ description: &apos;No book with that ID&apos; })
  findOne(@Param(&apos;id&apos;) id: string): Promise&amp;#x3C;Book&gt; {
    return this.booksService.findOne(id)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a shorthand decorator for every common status (&lt;code&gt;@ApiCreatedResponse&lt;/code&gt;, &lt;code&gt;@ApiForbiddenResponse&lt;/code&gt;, and so on), all built on &lt;code&gt;@ApiResponse({ status, ... })&lt;/code&gt;. For arrays, use &lt;code&gt;@ApiOkResponse({ type: Book, isArray: true })&lt;/code&gt; or &lt;code&gt;type: [Book]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is the most common gap in NestJS documents: the happy path is documented, and the &lt;code&gt;404&lt;/code&gt; thrown by &lt;code&gt;NotFoundException&lt;/code&gt; in the service is not. Every tool downstream then believes the endpoint cannot fail.&lt;/p&gt;
&lt;h2&gt;Make operation IDs stable&lt;/h2&gt;
&lt;p&gt;Each operation gets an &lt;code&gt;operationId&lt;/code&gt;. By default &lt;code&gt;@nestjs/swagger&lt;/code&gt; builds it as &lt;code&gt;{ControllerName}_{methodName}&lt;/code&gt;, so the handler above becomes &lt;code&gt;BooksController_findOne&lt;/code&gt;. That is unique and stable as long as you do not rename classes, but it is not what you want as a method name in a generated SDK.&lt;/p&gt;
&lt;p&gt;SDK generators name methods after operation IDs, and renaming one later is a breaking change for anyone using the client. Decide the scheme now with &lt;code&gt;operationIdFactory&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const documentFactory = () =&gt;
  SwaggerModule.createDocument(app, config, {
    operationIdFactory: (controllerKey: string, methodKey: string) =&gt; methodKey,
  })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using only &lt;code&gt;methodKey&lt;/code&gt; requires method names to be unique across controllers. If they are not (every controller has a &lt;code&gt;findOne&lt;/code&gt;), keep the controller in the ID but drop the &lt;code&gt;Controller&lt;/code&gt; suffix, or set IDs per route with &lt;code&gt;@ApiOperation({ operationId: &apos;getBook&apos; })&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Authentication&lt;/h2&gt;
&lt;p&gt;Declare the security schemes on the document, then attach them to controllers or handlers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const config = new DocumentBuilder()
  .setTitle(&apos;Bookstore API&apos;)
  .setVersion(&apos;1.0.0&apos;)
  .addBearerAuth()
  .addApiKey({ type: &apos;apiKey&apos;, name: &apos;X-API-Key&apos;, in: &apos;header&apos; }, &apos;api-key&apos;)
  .build()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;@ApiBearerAuth()
@Controller(&apos;orders&apos;)
export class OrdersController {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your guards still enforce authentication at runtime. The decorators only describe it, and Nest does not check that the two agree. When they drift, documentation UIs show the wrong auth form and generated clients ask for the wrong credentials. A test that compares guarded routes with the document&apos;s security requirements is worth writing if you have many of them.&lt;/p&gt;
&lt;h2&gt;Mapped types: import from the right package&lt;/h2&gt;
&lt;p&gt;Nest&apos;s mapped types (&lt;code&gt;PartialType&lt;/code&gt;, &lt;code&gt;PickType&lt;/code&gt;, &lt;code&gt;OmitType&lt;/code&gt;, &lt;code&gt;IntersectionType&lt;/code&gt;) are how most apps derive update and create DTOs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { PartialType } from &apos;@nestjs/swagger&apos;

export class UpdateBookDto extends PartialType(CreateBookDto) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import them from &lt;code&gt;@nestjs/swagger&lt;/code&gt;, not from &lt;code&gt;@nestjs/mapped-types&lt;/code&gt;. Both work at runtime, but only the &lt;code&gt;@nestjs/swagger&lt;/code&gt; versions carry the OpenAPI metadata across, so the other import produces an update DTO with no properties in the document. It is the single most common &quot;why is my schema empty&quot; answer.&lt;/p&gt;
&lt;h2&gt;Enums, generics and extra models&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Enums.&lt;/strong&gt; Give them a name so they become a reusable schema instead of an inline list repeated everywhere:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;@ApiProperty({ enum: BookFormat, enumName: &apos;BookFormat&apos; })
format: BookFormat&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Generic responses.&lt;/strong&gt; TypeScript generics are erased, so a &lt;code&gt;Paginated&amp;#x3C;Book&gt;&lt;/code&gt; wrapper cannot be discovered. Register the inner model with &lt;code&gt;@ApiExtraModels(Book)&lt;/code&gt; and compose the response with &lt;code&gt;allOf&lt;/code&gt; and &lt;code&gt;getSchemaPath(Book)&lt;/code&gt;. The &lt;a href=&quot;https://docs.nestjs.com/openapi/operations&quot;&gt;operations page&lt;/a&gt; has a complete example, including a reusable &lt;code&gt;ApiPaginatedResponse&lt;/code&gt; decorator. Wrap it once and reuse it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hiding things.&lt;/strong&gt; &lt;code&gt;@ApiExcludeEndpoint()&lt;/code&gt; and &lt;code&gt;@ApiExcludeController()&lt;/code&gt; remove routes from the document. &lt;code&gt;@ApiHideProperty()&lt;/code&gt; removes a property, which you need when the CLI plugin would otherwise document an internal field.&lt;/p&gt;
&lt;h2&gt;Export the document for CI&lt;/h2&gt;
&lt;p&gt;The served document is fine for development. For CI you want a file: something you can lint, diff against the last release, commit, and hand to other tools. The simplest approach is a small script that builds the app without listening:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// scripts/export-openapi.ts
import { writeFileSync } from &apos;node:fs&apos;
import { NestFactory } from &apos;@nestjs/core&apos;
import { DocumentBuilder, SwaggerModule } from &apos;@nestjs/swagger&apos;
import { AppModule } from &apos;../src/app.module&apos;

async function main() {
  const app = await NestFactory.create(AppModule, { logger: false })
  const config = new DocumentBuilder().setTitle(&apos;Bookstore API&apos;).setVersion(&apos;1.0.0&apos;).build()
  const document = SwaggerModule.createDocument(app, config)
  writeFileSync(&apos;openapi.json&apos;, JSON.stringify(document, null, 2))
  await app.close()
}
main()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep the &lt;code&gt;DocumentBuilder&lt;/code&gt; configuration in one shared function so &lt;code&gt;main.ts&lt;/code&gt; and this script cannot drift. If your modules connect to a database on startup, you will need to stub those providers or use a test module here. Then lint the file in CI (our &lt;a href=&quot;https://scalar.com/learn/openapi/spectral-rules&quot;&gt;Spectral rules guide&lt;/a&gt; has a starting ruleset) and compare it with the previous release to catch breaking changes.&lt;/p&gt;
&lt;h2&gt;Common mistakes&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Interfaces as DTOs.&lt;/strong&gt; They vanish at runtime. Use classes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DTO files without the &lt;code&gt;.dto.ts&lt;/code&gt; suffix&lt;/strong&gt; when relying on the CLI plugin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapped types from &lt;code&gt;@nestjs/mapped-types&lt;/code&gt;.&lt;/strong&gt; The derived DTO shows up empty.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No response decorators.&lt;/strong&gt; Return types are invisible to Nest, so the document has no response schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default operation IDs in a public SDK.&lt;/strong&gt; &lt;code&gt;BooksController_findOne&lt;/code&gt; becomes a method name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth decorators that disagree with guards.&lt;/strong&gt; Nothing checks them against each other.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;What to do with the document&lt;/h2&gt;
&lt;p&gt;Now you have an accurate OpenAPI document coming out of your app and your CI. The value is in what reads it:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A documentation UI.&lt;/strong&gt; &lt;code&gt;@nestjs/swagger&lt;/code&gt; serves Swagger UI. Set &lt;code&gt;ui: false&lt;/code&gt; and you can serve any other UI that reads OpenAPI from the same JSON route.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client SDKs&lt;/strong&gt; for the languages your users write, generated from the document instead of maintained by hand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; so AI agents can call the operations you choose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract tests and breaking-change checks&lt;/strong&gt; against the last published document.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Part two of this series covers each of those. If you want the short version today, the &lt;a href=&quot;https://scalar.com/docs-for/nestjs&quot;&gt;NestJS API documentation&lt;/a&gt; page shows the setup, and the &lt;a href=&quot;https://scalar.com/products/api-references/integrations/nestjs&quot;&gt;NestJS integration guide&lt;/a&gt; lists every option.&lt;/p&gt;
&lt;h2&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Where is the NestJS OpenAPI JSON document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you call &lt;code&gt;SwaggerModule.setup(&apos;api&apos;, ...)&lt;/code&gt;, the JSON is at &lt;code&gt;/api-json&lt;/code&gt; and YAML at &lt;code&gt;/api-yaml&lt;/code&gt;. Change them with the &lt;code&gt;jsonDocumentUrl&lt;/code&gt; and &lt;code&gt;yamlDocumentUrl&lt;/code&gt; options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is @nestjs/swagger OpenAPI or Swagger?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It generates an OpenAPI 3.x document. The package name and the bundled Swagger UI keep the older &quot;Swagger&quot; name.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why are my NestJS DTO properties missing from the schema?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Usually one of three things: the DTO is an interface rather than a class, the file does not end in &lt;code&gt;.dto.ts&lt;/code&gt; or &lt;code&gt;.entity.ts&lt;/code&gt; while you rely on the CLI plugin, or a mapped type was imported from &lt;code&gt;@nestjs/mapped-types&lt;/code&gt; instead of &lt;code&gt;@nestjs/swagger&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I change operation IDs in NestJS?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pass &lt;code&gt;operationIdFactory&lt;/code&gt; in the options to &lt;code&gt;SwaggerModule.createDocument&lt;/code&gt;, or set &lt;code&gt;operationId&lt;/code&gt; in &lt;code&gt;@ApiOperation()&lt;/code&gt; on a single handler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I serve the OpenAPI document without Swagger UI?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. Pass &lt;code&gt;{ ui: false }&lt;/code&gt; as the options to &lt;code&gt;SwaggerModule.setup&lt;/code&gt; and only the document routes are served.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which OpenAPI version does NestJS generate?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The document declares OpenAPI 3.0.0 by default. &lt;code&gt;DocumentBuilder.setOpenAPIVersion()&lt;/code&gt; changes the declared version; validate the output against that version before relying on it.&lt;/p&gt;
&lt;h2&gt;Related&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/learn/openapi/what-is-openapi&quot;&gt;What is OpenAPI?&lt;/a&gt; · &lt;a href=&quot;https://scalar.com/learn/openapi/openapi-vs-swagger&quot;&gt;OpenAPI vs Swagger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/api-references/integrations/nestjs&quot;&gt;NestJS integration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/docs-for/nestjs&quot;&gt;NestJS API documentation&lt;/a&gt; — turn the document from this post into interactive docs with one package&lt;/li&gt;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>OpenAPI with FastAPI</title>
      <link>https://scalar.com/blog/posts/2026-10-08-openapi-with-fastapi</link>
      <guid>https://scalar.com/blog/posts/2026-10-08-openapi-with-fastapi</guid>
      <pubDate>Thu, 08 Oct 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;FastAPI generates an OpenAPI document for your API automatically, from the type hints, Pydantic models and decorators you already write. Start any FastAPI app and the document is at &lt;code&gt;/openapi.json&lt;/code&gt;. The interesting work is not turning it on. It is making the document accurate enough that other tools can rely on it.&lt;/p&gt;
&lt;p&gt;This is part one of a three-part series and it is vendor-neutral. Everything below is plain FastAPI and Pydantic, and it applies whatever you later point at the document: a documentation UI, an SDK generator, a linter, or a contract test. Part two covers what you can build from the document, and part three walks through one concrete setup.&lt;/p&gt;
&lt;p&gt;Behaviour described here matches the &lt;a href=&quot;https://fastapi.tiangolo.com/&quot;&gt;FastAPI documentation&lt;/a&gt; and &lt;a href=&quot;https://fastapi.tiangolo.com/release-notes/&quot;&gt;release notes&lt;/a&gt; as of September 2026, when the latest release was 0.141.1.&lt;/p&gt;
&lt;h2&gt;How FastAPI builds the document&lt;/h2&gt;
&lt;p&gt;FastAPI walks every route registered on the app, and for each one it collects:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;the path, the HTTP method, and path, query, header and cookie parameters from the function signature,&lt;/li&gt;
&lt;li&gt;the request body schema from Pydantic models,&lt;/li&gt;
&lt;li&gt;the response schema from the return type annotation or &lt;code&gt;response_model&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;any metadata you pass to the decorator (summary, tags, responses, and so on).&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Pydantic produces JSON Schema for every model, and FastAPI assembles those schemas into &lt;code&gt;components.schemas&lt;/code&gt; with the paths around them. The result is generated on the first request to &lt;code&gt;/openapi.json&lt;/code&gt; and cached on &lt;code&gt;app.openapi_schema&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://github.com/fastapi/fastapi/releases/tag/0.99.0&quot;&gt;FastAPI 0.99.0&lt;/a&gt;, the output is OpenAPI 3.1.0, which means schemas are JSON Schema 2020-12. Nullable fields come out as &lt;code&gt;anyOf&lt;/code&gt; with a &lt;code&gt;null&lt;/code&gt; type rather than the 3.0-only &lt;code&gt;nullable: true&lt;/code&gt;. That is correct and matches what Pydantic v2 produces natively, but it catches people who feed the document to a tool that only reads 3.0. More on that below.&lt;/p&gt;
&lt;h2&gt;The minimum app&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title=&quot;Bookstore API&quot;, version=&quot;1.0.0&quot;)


class Book(BaseModel):
    id: int
    title: str
    subtitle: str | None = None


@app.get(&quot;/books/{book_id}&quot;)
def get_book(book_id: int) -&gt; Book:
    return Book(id=book_id, title=&quot;Dune&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it with &lt;code&gt;fastapi dev main.py&lt;/code&gt; and you get three URLs:&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Controlled by&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/openapi.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The OpenAPI document&lt;/td&gt;
&lt;td&gt;&lt;code&gt;openapi_url&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Swagger UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs_url&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/redoc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ReDoc&lt;/td&gt;
&lt;td&gt;&lt;code&gt;redoc_url&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Set any of them to &lt;code&gt;None&lt;/code&gt; to turn it off. Setting &lt;code&gt;openapi_url=None&lt;/code&gt; disables the document and both UIs, because the UIs read from it.&lt;/p&gt;
&lt;h2&gt;Describe the API, not only the types&lt;/h2&gt;
&lt;p&gt;A document generated from types alone is valid but thin. Here is what to add, ordered by how much it helps the people and tools that read it.&lt;/p&gt;
&lt;h3&gt;App-level metadata&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;app = FastAPI(
    title=&quot;Bookstore API&quot;,
    version=&quot;1.0.0&quot;,
    summary=&quot;Books, authors and orders.&quot;,
    description=&quot;Use this API to manage the catalogue and place orders.&quot;,
    contact={&quot;name&quot;: &quot;API team&quot;, &quot;email&quot;: &quot;api@example.com&quot;},
    license_info={&quot;name&quot;: &quot;MIT&quot;, &quot;identifier&quot;: &quot;MIT&quot;},
    openapi_tags=[
        {&quot;name&quot;: &quot;books&quot;, &quot;description&quot;: &quot;Browse and manage the catalogue.&quot;},
        {&quot;name&quot;: &quot;orders&quot;, &quot;description&quot;: &quot;Place and track orders.&quot;},
    ],
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;openapi_tags&lt;/code&gt; sets the order and description of tag groups. Documentation UIs use it for navigation, and several SDK generators use tags to group methods into namespaces. &lt;code&gt;description&lt;/code&gt; accepts Markdown.&lt;/p&gt;
&lt;h3&gt;Per-operation metadata&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.get(
    &quot;/books/{book_id}&quot;,
    tags=[&quot;books&quot;],
    summary=&quot;Get a book&quot;,
    response_description=&quot;The requested book&quot;,
    responses={404: {&quot;description&quot;: &quot;Book not found&quot;}},
)
def get_book(book_id: int) -&gt; Book:
    &quot;&quot;&quot;
    Returns a single book by ID.

    Books that have been removed from the catalogue return **404**.
    &quot;&quot;&quot;
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The docstring becomes the operation description, and Markdown in it is preserved. &lt;code&gt;summary&lt;/code&gt; defaults to a title-cased version of the function name, which is fine for &lt;code&gt;get_book&lt;/code&gt; and less fine for &lt;code&gt;handle_v2_lookup&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Document every response, not only the happy path&lt;/h3&gt;
&lt;p&gt;This is the most common gap in FastAPI documents. FastAPI knows the success response from your return type, and it adds a &lt;code&gt;422&lt;/code&gt; validation error response to every operation that takes parameters or a body. It does not know about the &lt;code&gt;404&lt;/code&gt; you raise with &lt;code&gt;HTTPException&lt;/code&gt; inside the function, because that happens at runtime.&lt;/p&gt;
&lt;p&gt;Declare those with &lt;code&gt;responses&lt;/code&gt;, and give them a model when the body has a shape:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ErrorMessage(BaseModel):
    detail: str


@app.get(
    &quot;/books/{book_id}&quot;,
    responses={404: {&quot;model&quot;: ErrorMessage, &quot;description&quot;: &quot;Book not found&quot;}},
)
def get_book(book_id: int) -&gt; Book:
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If most of your endpoints share error responses, define the dictionary once and merge it in: &lt;code&gt;responses={**common_errors, 404: {...}}&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Parameters and fields&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;Annotated&lt;/code&gt; with &lt;code&gt;Path&lt;/code&gt;, &lt;code&gt;Query&lt;/code&gt;, &lt;code&gt;Header&lt;/code&gt; and Pydantic&apos;s &lt;code&gt;Field&lt;/code&gt; to add descriptions, constraints and examples. Constraints become schema keywords that validators and generated clients understand:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Annotated
from fastapi import Path, Query
from pydantic import Field


class Book(BaseModel):
    id: int = Field(description=&quot;Unique identifier&quot;)
    title: str = Field(max_length=200, examples=[&quot;Dune&quot;])
    subtitle: str | None = Field(default=None, examples=[&quot;Book one&quot;])


@app.get(&quot;/books&quot;)
def list_books(
    limit: Annotated[int, Query(ge=1, le=100, description=&quot;Page size&quot;)] = 20,
    cursor: Annotated[str | None, Query(description=&quot;Cursor from the previous page&quot;)] = None,
) -&gt; list[Book]:
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Examples in Pydantic &lt;code&gt;Field(examples=[...])&lt;/code&gt; go into the JSON Schema. If you want OpenAPI&apos;s named examples on a request body (several labelled examples that a UI can switch between), use &lt;code&gt;Body(openapi_examples={...})&lt;/code&gt; instead.&lt;/p&gt;
&lt;h2&gt;Make operation IDs stable&lt;/h2&gt;
&lt;p&gt;Every operation in the document has an &lt;code&gt;operationId&lt;/code&gt;. FastAPI generates it from the function name, the path and the method, so &lt;code&gt;get_book&lt;/code&gt; on &lt;code&gt;GET /books/{book_id}&lt;/code&gt; becomes something like &lt;code&gt;get_book_books__book_id__get&lt;/code&gt;. That is unique, which is what the spec requires, but it is not pleasant, and it changes if you rename the function or move the path.&lt;/p&gt;
&lt;p&gt;It matters because SDK generators name methods after operation IDs. An unstable ID is an unstable SDK method name, which is a breaking change for anybody using a generated client. FastAPI&apos;s own &lt;a href=&quot;https://fastapi.tiangolo.com/advanced/generate-clients/&quot;&gt;SDK generation guide&lt;/a&gt; recommends a custom &lt;code&gt;generate_unique_id_function&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import FastAPI
from fastapi.routing import APIRoute


def custom_generate_unique_id(route: APIRoute) -&gt; str:
    return f&quot;{route.tags[0]}-{route.name}&quot;


app = FastAPI(generate_unique_id_function=custom_generate_unique_id)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gives you &lt;code&gt;books-get_book&lt;/code&gt;, and it only changes when you rename the function or its tag on purpose. Note that this example assumes every route has at least one tag; add a fallback if yours do not. You can also pass &lt;code&gt;operation_id=&quot;getBook&quot;&lt;/code&gt; to a single decorator.&lt;/p&gt;
&lt;h2&gt;Authentication&lt;/h2&gt;
&lt;p&gt;FastAPI&apos;s security utilities are dependencies that also register security schemes in the document. Use them rather than reading headers by hand, and the document describes your auth correctly for free:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi import Depends
from fastapi.security import APIKeyHeader, HTTPBearer

api_key = APIKeyHeader(name=&quot;X-API-Key&quot;)
bearer = HTTPBearer()


@app.get(&quot;/orders&quot;)
def list_orders(key: str = Depends(api_key)) -&gt; list[Order]:
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This adds an &lt;code&gt;apiKey&lt;/code&gt; scheme to &lt;code&gt;components.securitySchemes&lt;/code&gt; and a security requirement to the operation. &lt;code&gt;OAuth2PasswordBearer(tokenUrl=&quot;token&quot;)&lt;/code&gt; does the same for the OAuth 2.0 password flow. Documentation UIs read these to show an authentication form, and SDK generators read them to decide what the client constructor asks for.&lt;/p&gt;
&lt;p&gt;If you read &lt;code&gt;request.headers[&quot;Authorization&quot;]&lt;/code&gt; directly instead, the endpoint works but the document says it is unauthenticated, and every tool downstream believes it.&lt;/p&gt;
&lt;h2&gt;Input and output schemas&lt;/h2&gt;
&lt;p&gt;Since &lt;a href=&quot;https://fastapi.tiangolo.com/how-to/separate-openapi-schemas/&quot;&gt;FastAPI 0.102.0&lt;/a&gt;, a model used for both requests and responses can produce two schemas, &lt;code&gt;Book-Input&lt;/code&gt; and &lt;code&gt;Book-Output&lt;/code&gt;. The reason is honest: a field with a default is optional when you send it, but always present when the server returns it. Two schemas describe that precisely.&lt;/p&gt;
&lt;p&gt;If your consumers find the split confusing, or a generator produces awkward type names from it, turn it off:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;app = FastAPI(separate_input_output_schemas=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You get a single &lt;code&gt;Book&lt;/code&gt; schema, and fields with defaults are not marked as required. The cleaner fix, if the input and output really differ, is to define two models (&lt;code&gt;BookCreate&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt;) and name them yourself.&lt;/p&gt;
&lt;h2&gt;Webhooks&lt;/h2&gt;
&lt;p&gt;OpenAPI 3.1 has a top-level &lt;code&gt;webhooks&lt;/code&gt; section for requests your API sends to other people. FastAPI supports it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class OrderEvent(BaseModel):
    order_id: int
    status: str


@app.webhooks.post(&quot;order-updated&quot;)
def order_updated(body: OrderEvent):
    &quot;&quot;&quot;Sent when an order changes status.&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function is never called. It exists so the document describes the payload your subscribers will receive.&lt;/p&gt;
&lt;h2&gt;Customize the whole document&lt;/h2&gt;
&lt;p&gt;When decorator arguments are not enough (adding a vendor extension, a logo, or a &lt;code&gt;servers&lt;/code&gt; list that differs per environment), override &lt;code&gt;app.openapi&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastapi.openapi.utils import get_openapi


def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    schema = get_openapi(
        title=app.title,
        version=app.version,
        summary=app.summary,
        description=app.description,
        routes=app.routes,
    )
    schema[&quot;servers&quot;] = [{&quot;url&quot;: &quot;https://api.example.com&quot;}]
    app.openapi_schema = schema
    return app.openapi_schema


app.openapi = custom_openapi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a single operation, &lt;code&gt;openapi_extra={...}&lt;/code&gt; on the decorator merges arbitrary keys into that operation. It is the right tool for vendor extensions such as &lt;code&gt;x-codeSamples&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you run behind a proxy with a path prefix, set &lt;code&gt;root_path&lt;/code&gt; (or pass &lt;code&gt;--root-path&lt;/code&gt; to the server) so the document&apos;s &lt;code&gt;servers&lt;/code&gt; entry points at the public URL rather than the internal one.&lt;/p&gt;
&lt;h2&gt;Export the document for CI&lt;/h2&gt;
&lt;p&gt;Serving the document from a running app is fine for development. For CI you want a file you can lint, diff against the last release, commit, and hand to other tools. FastAPI can produce it without starting a server:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;python&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# scripts/export_openapi.py
import json
from main import app

with open(&quot;openapi.json&quot;, &quot;w&quot;) as f:
    json.dump(app.openapi(), f, indent=2)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it in CI after tests, fail the build if the linter complains, and compare the result with the previous release to catch breaking changes before your users do. Our &lt;a href=&quot;https://scalar.com/learn/openapi/spectral-rules&quot;&gt;Spectral rules guide&lt;/a&gt; has a sensible starting ruleset.&lt;/p&gt;
&lt;h2&gt;Common mistakes&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Raising &lt;code&gt;HTTPException&lt;/code&gt; without declaring the response.&lt;/strong&gt; The endpoint works; the document says it can only succeed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leaving operation IDs to the default.&lt;/strong&gt; Fine for a demo, painful for an SDK.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Returning &lt;code&gt;dict&lt;/code&gt; instead of a model.&lt;/strong&gt; A function annotated &lt;code&gt;-&gt; dict&lt;/code&gt; produces an untyped response schema. Return a Pydantic model and the document gets a real schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reading auth headers by hand.&lt;/strong&gt; Use the &lt;code&gt;fastapi.security&lt;/code&gt; dependencies so the scheme is documented.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feeding 3.1 output to a 3.0-only tool.&lt;/strong&gt; Upgrade the tool if you can. If you cannot, convert the file rather than downgrading your app&apos;s output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hiding internals with &lt;code&gt;include_in_schema=False&lt;/code&gt; and forgetting about it.&lt;/strong&gt; It hides the route from the document, not from the internet.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;What to do with the document&lt;/h2&gt;
&lt;p&gt;Now you have an accurate OpenAPI 3.1 document coming out of your app and your CI. The value is in what reads it:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A documentation UI.&lt;/strong&gt; FastAPI ships Swagger UI and ReDoc. You can serve any other UI that reads OpenAPI from a route with &lt;code&gt;include_in_schema=False&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client SDKs&lt;/strong&gt; in Python, TypeScript and the other languages your users write.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; so AI agents can call the operations you choose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract tests and breaking-change checks&lt;/strong&gt; against the last published document.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Part two of this series covers each of those. For the short version today, the &lt;a href=&quot;https://scalar.com/docs-for/fastapi&quot;&gt;FastAPI API documentation&lt;/a&gt; page shows a two-line setup, and the &lt;a href=&quot;https://scalar.com/products/api-references/integrations/fastapi&quot;&gt;FastAPI integration guide&lt;/a&gt; lists every option.&lt;/p&gt;
&lt;h2&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Where is the FastAPI OpenAPI document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At &lt;code&gt;/openapi.json&lt;/code&gt; by default. Change it with &lt;code&gt;FastAPI(openapi_url=&quot;/api/openapi.json&quot;)&lt;/code&gt;, or disable it with &lt;code&gt;openapi_url=None&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which OpenAPI version does FastAPI generate?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenAPI 3.1.0, since FastAPI 0.99.0. There is no built-in switch to emit 3.0.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I export the FastAPI OpenAPI document to a file?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Import the app and call &lt;code&gt;app.openapi()&lt;/code&gt;, then write the result with &lt;code&gt;json.dump&lt;/code&gt;. No server is needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why are there Model-Input and Model-Output schemas in my document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since FastAPI 0.102.0, a model used for both requests and responses gets separate schemas, because fields with defaults are optional on input but always present on output. Pass &lt;code&gt;separate_input_output_schemas=False&lt;/code&gt; to get one schema per model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I change FastAPI operation IDs?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pass &lt;code&gt;generate_unique_id_function&lt;/code&gt; to &lt;code&gt;FastAPI()&lt;/code&gt; or &lt;code&gt;APIRouter()&lt;/code&gt; to change them everywhere, or &lt;code&gt;operation_id=&quot;...&quot;&lt;/code&gt; on a single route decorator.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I add authentication to the FastAPI OpenAPI document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use the dependencies in &lt;code&gt;fastapi.security&lt;/code&gt;, such as &lt;code&gt;APIKeyHeader&lt;/code&gt;, &lt;code&gt;HTTPBearer&lt;/code&gt; or &lt;code&gt;OAuth2PasswordBearer&lt;/code&gt;. They validate the request and register the matching security scheme in the document.&lt;/p&gt;
&lt;h2&gt;Related&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/learn/openapi/what-is-openapi&quot;&gt;What is OpenAPI?&lt;/a&gt; · &lt;a href=&quot;https://scalar.com/learn/sdk/generate-sdk-from-openapi&quot;&gt;Generate an SDK from OpenAPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/api-references/integrations/fastapi&quot;&gt;FastAPI integration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/docs-for/fastapi&quot;&gt;FastAPI API documentation&lt;/a&gt; — turn the document from this post into interactive docs with one package&lt;/li&gt;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>OpenAPI with ASP.NET Core in .NET 9 and 10</title>
      <link>https://scalar.com/blog/posts/2026-10-06-openapi-aspnet-core-dotnet-9-10</link>
      <guid>https://scalar.com/blog/posts/2026-10-06-openapi-aspnet-core-dotnet-9-10</guid>
      <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;ASP.NET Core generates OpenAPI documents on its own now. Since .NET 9, the &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; package turns your minimal APIs and controllers into an OpenAPI document at runtime or at build time, and .NET 10 moved the default output to OpenAPI 3.1 and added XML comment support. You no longer need a third-party generator to describe your API.&lt;/p&gt;
&lt;p&gt;This is part one of a three-part series. It is deliberately vendor-neutral: everything here uses Microsoft&apos;s packages and works the same whichever documentation UI, SDK generator, or linter you point at the result. Part two covers what you can build from the document (reference docs, SDKs, an MCP server), and part three walks through one concrete setup end to end.&lt;/p&gt;
&lt;p&gt;Every API name below was checked against the Microsoft Learn pages for &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-10.0&quot;&gt;generating OpenAPI documents&lt;/a&gt;, &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-10.0&quot;&gt;customizing them&lt;/a&gt;, &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-10.0&quot;&gt;adding endpoint metadata&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/openapi-comments?view=aspnetcore-10.0&quot;&gt;XML comment support&lt;/a&gt; as of September 2026.&lt;/p&gt;
&lt;h2&gt;Why Swashbuckle stopped being the default&lt;/h2&gt;
&lt;p&gt;For most of the ASP.NET Core era, the Web API template shipped with Swashbuckle, a community project that both generated the document and served Swagger UI. In .NET 9 the ASP.NET Core team &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/54599&quot;&gt;removed it from the template&lt;/a&gt; and replaced it with first-party generation in &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That split matters more than it first looks. The new package produces the document and nothing else. It does not render a UI. That is a good separation: the document is the contract, and what you do with it (docs, client generation, contract tests, linting) is a separate choice. It also means a fresh .NET 9 or 10 project has an OpenAPI endpoint and no page to look at it with, which surprises people the first time.&lt;/p&gt;
&lt;p&gt;If you are on an existing Swashbuckle project, nothing forces you to move. Swashbuckle still works. The reasons to switch are the ones below: 3.1 output, AoT compatibility, and a transformer API maintained by the same team that ships the framework.&lt;/p&gt;
&lt;h2&gt;.NET 9 vs .NET 10 at a glance&lt;/h2&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;
&lt;th&gt;.NET 9&lt;/th&gt;
&lt;th&gt;.NET 10&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Package&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Default OpenAPI version&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;3.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Version you can switch to&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JSON Schema dialect&lt;/td&gt;
&lt;td&gt;OpenAPI 3.0 schema subset&lt;/td&gt;
&lt;td&gt;JSON Schema draft 2020-12&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nullable types&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nullable: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;type: [&quot;null&quot;, &quot;string&quot;]&lt;/code&gt; (3.1)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;XML doc comments&lt;/td&gt;
&lt;td&gt;Not read&lt;/td&gt;
&lt;td&gt;Read by a source generator&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;YAML endpoint&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, via &lt;code&gt;.yaml&lt;/code&gt; route suffix&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Object model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Microsoft.OpenApi&lt;/code&gt; 1.x (&lt;code&gt;Microsoft.OpenApi.Models&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Microsoft.OpenApi&lt;/code&gt; 2.x (&lt;code&gt;Microsoft.OpenApi&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Microsoft&apos;s .NET 11 documentation already describes OpenAPI 3.2 as the default for that release. If you are reading this after .NET 11 ships, the pattern is the same; only the default changes.&lt;/p&gt;
&lt;h2&gt;Add OpenAPI generation&lt;/h2&gt;
&lt;p&gt;Install the package:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;shell&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet add package Microsoft.AspNetCore.OpenApi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then register the services and map the endpoint in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

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

app.Run();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the app and open &lt;code&gt;https://localhost:{port}/openapi/v1.json&lt;/code&gt;. &lt;code&gt;v1&lt;/code&gt; is the default document name. The &lt;code&gt;IsDevelopment()&lt;/code&gt; guard is Microsoft&apos;s recommendation, not a technical requirement: it keeps the document off production by default so you opt in to publishing it.&lt;/p&gt;
&lt;p&gt;Two small things the generator does that are easy to miss. It excludes HTTP methods OpenAPI does not recognise (Microsoft&apos;s example is &lt;code&gt;QUERY&lt;/code&gt;) rather than emitting something invalid. And it formats numbers and dates with the invariant culture, so the document is identical whatever locale your build agent runs in. That second one matters when you diff documents in CI.&lt;/p&gt;
&lt;h2&gt;Choose the OpenAPI version&lt;/h2&gt;
&lt;p&gt;On .NET 10 you get 3.1 unless you ask otherwise. Some downstream tools still only read 3.0, and in that case you can pin it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder.Services.AddOpenApi(options =&gt;
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our advice is to stay on 3.1 unless a specific consumer breaks. OpenAPI 3.1 aligns schemas with JSON Schema 2020-12, which is why nullable reference types come out as &lt;code&gt;type: [&quot;null&quot;, &quot;string&quot;]&lt;/code&gt; instead of the 3.0-only &lt;code&gt;nullable: true&lt;/code&gt; keyword. Validators, generators and editors increasingly treat 3.1 as the baseline. If you want the full list of differences, we wrote them up in &lt;a href=&quot;https://scalar.com/learn/openapi/openapi-3-1-vs-3-0&quot;&gt;OpenAPI 3.1 vs 3.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On .NET 9 the default is 3.0 and there is no 3.1 option, so the only way to get 3.1 output is to upgrade.&lt;/p&gt;
&lt;h2&gt;Serve JSON, YAML, or both&lt;/h2&gt;
&lt;p&gt;The document is served as JSON by default. On .NET 10 you can serve YAML by giving the route a &lt;code&gt;.yaml&lt;/code&gt; or &lt;code&gt;.yml&lt;/code&gt; suffix:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;app.MapOpenApi(&quot;/openapi/{documentName}.yaml&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Call &lt;code&gt;MapOpenApi()&lt;/code&gt; twice if you want both. You can also move the route (&lt;code&gt;app.MapOpenApi(&quot;/openapi/{documentName}/openapi.json&quot;)&lt;/code&gt;); keep the &lt;code&gt;{documentName}&lt;/code&gt; parameter in it, because without it the framework falls back to a query parameter and Microsoft describes the result as unpredictable.&lt;/p&gt;
&lt;h2&gt;Describe your endpoints properly&lt;/h2&gt;
&lt;p&gt;A generated document is only as good as the metadata it can see. Without any, you get paths and types and very little else: no summaries, no operation IDs, and response codes only where the framework can infer them. Here is what to add, roughly in order of payoff.&lt;/p&gt;
&lt;h3&gt;Return typed results&lt;/h3&gt;
&lt;p&gt;For minimal APIs, &lt;code&gt;TypedResults&lt;/code&gt; and the &lt;code&gt;Results&amp;#x3C;...&gt;&lt;/code&gt; union type carry response metadata at compile time, so the generator knows every status code an endpoint can return without you repeating it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;app.MapGet(&quot;/books/{id}&quot;, Results&amp;#x3C;Ok&amp;#x3C;Book&gt;, NotFound&gt; (int id, BookStore store) =&gt;
    store.Find(id) is Book book
        ? TypedResults.Ok(book)
        : TypedResults.NotFound());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the single most useful habit. It keeps the document honest because the compiler, not a comment, decides what the endpoint returns.&lt;/p&gt;
&lt;h3&gt;Add summaries, descriptions, tags and operation IDs&lt;/h3&gt;
&lt;p&gt;Use the extension methods:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;app.MapGet(&quot;/books/{id}&quot;, GetBook)
    .WithName(&quot;GetBook&quot;)
    .WithSummary(&quot;Get a book by ID&quot;)
    .WithDescription(&quot;Returns a single book, or 404 if the ID is unknown.&quot;)
    .WithTags(&quot;books&quot;)
    .ProducesProblem(StatusCodes.Status500InternalServerError);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the attribute equivalents on the handler: &lt;code&gt;[EndpointName]&lt;/code&gt;, &lt;code&gt;[EndpointSummary]&lt;/code&gt;, &lt;code&gt;[EndpointDescription]&lt;/code&gt;, &lt;code&gt;[Tags]&lt;/code&gt;, and &lt;code&gt;[Description]&lt;/code&gt; on parameters. &lt;code&gt;WithName&lt;/code&gt; sets the &lt;code&gt;operationId&lt;/code&gt;. Set it deliberately. SDK generators use it to name methods, so &lt;code&gt;GetBook&lt;/code&gt; becomes &lt;code&gt;getBook()&lt;/code&gt; in TypeScript and &lt;code&gt;get_book()&lt;/code&gt; in Python, and changing it later is a breaking change for anybody using a generated client.&lt;/p&gt;
&lt;p&gt;For controllers, keep using &lt;code&gt;[ProducesResponseType]&lt;/code&gt; for each status code, as you did with Swashbuckle.&lt;/p&gt;
&lt;p&gt;To hide an endpoint from the document, use &lt;code&gt;.ExcludeFromDescription()&lt;/code&gt; or the &lt;code&gt;[ExcludeFromDescription]&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h3&gt;Let the models describe themselves&lt;/h3&gt;
&lt;p&gt;Data annotations flow into the schema. &lt;code&gt;[Required]&lt;/code&gt; marks a property as required, &lt;code&gt;[Description]&lt;/code&gt; sets its description, and validation attributes such as &lt;code&gt;[MaxLength]&lt;/code&gt; become schema constraints:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public record Book(
    [property: Required]
    [property: Description(&quot;The unique identifier for the book&quot;)]
    int Id,
    [property: Description(&quot;The book title&quot;)]
    [property: MaxLength(200)]
    string Title,
    string? Subtitle);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On .NET 10, &lt;code&gt;Subtitle&lt;/code&gt; comes out as &lt;code&gt;type: [&quot;null&quot;, &quot;string&quot;]&lt;/code&gt;. The generator also infers &lt;code&gt;required&lt;/code&gt; from constructor parameters when a type has exactly one public constructor, which is why records often need fewer attributes than you would expect. With more than one constructor, nothing is inferred.&lt;/p&gt;
&lt;h3&gt;Use XML comments (.NET 10)&lt;/h3&gt;
&lt;p&gt;.NET 10 reads the XML doc comments you probably already write. Enable the documentation file in the project:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;xml&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;PropertyGroup&gt;
  &amp;#x3C;GenerateDocumentationFile&gt;true&amp;#x3C;/GenerateDocumentationFile&gt;
&amp;#x3C;/PropertyGroup&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is no extra code. A source generator intercepts your &lt;code&gt;AddOpenApi()&lt;/code&gt; calls at compile time and adds the comments as summaries, descriptions, parameter docs and response descriptions. Supported tags include &lt;code&gt;&amp;#x3C;summary&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;remarks&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;param&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;returns&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;response&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;example&gt;&lt;/code&gt; and &lt;code&gt;&amp;#x3C;deprecated&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two gotchas from the Microsoft docs that catch people:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;A &lt;code&gt;&amp;#x3C;response code=&quot;404&quot;&gt;&lt;/code&gt; tag only &lt;em&gt;describes&lt;/em&gt; a response. It does not declare one. If the endpoint&apos;s metadata does not already include a 404 (from a typed result, &lt;code&gt;Produces&lt;/code&gt;, or &lt;code&gt;[ProducesResponseType]&lt;/code&gt;), the comment is silently dropped.&lt;/li&gt;
&lt;li&gt;The source generator only recognises document names that are string literals. &lt;code&gt;AddOpenApi(&quot;v1&quot;)&lt;/code&gt; works; &lt;code&gt;AddOpenApi(documentName)&lt;/code&gt; with a variable gets no XML comments at all.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Customize the document with transformers&lt;/h2&gt;
&lt;p&gt;Sometimes metadata is not enough. You need to add a security scheme, set the &lt;code&gt;info&lt;/code&gt; block, stamp every operation with a header, or fix how a type maps to a schema. That is what transformers are for. There are three kinds, and they run in a fixed order: schema transformers first, then operation transformers, then document transformers.&lt;/p&gt;
&lt;p&gt;A document transformer that sets the title and description:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder.Services.AddOpenApi(options =&gt;
{
    options.AddDocumentTransformer((document, context, cancellationToken) =&gt;
    {
        document.Info = new()
        {
            Title = &quot;Bookstore API&quot;,
            Version = &quot;v1&quot;,
            Description = &quot;Books, authors and orders.&quot;
        };
        return Task.CompletedTask;
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An operation transformer that adds a 500 response everywhere:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;options.AddOperationTransformer((operation, context, cancellationToken) =&gt;
{
    operation.Responses ??= new OpenApiResponses();
    operation.Responses.Add(&quot;500&quot;, new OpenApiResponse { Description = &quot;Internal server error&quot; });
    return Task.CompletedTask;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A schema transformer that gives &lt;code&gt;decimal&lt;/code&gt; a &lt;code&gt;decimal&lt;/code&gt; format instead of &lt;code&gt;double&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;options.AddSchemaTransformer((schema, context, cancellationToken) =&gt;
{
    if (context.JsonTypeInfo.Type == typeof(decimal))
    {
        schema.Format = &quot;decimal&quot;;
    }
    return Task.CompletedTask;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For anything that needs services from dependency injection, implement &lt;code&gt;IOpenApiDocumentTransformer&lt;/code&gt; (or the operation and schema equivalents) and register it with &lt;code&gt;AddDocumentTransformer&amp;#x3C;T&gt;()&lt;/code&gt;. Microsoft&apos;s own example uses this to read the registered authentication schemes and add a matching bearer security scheme to &lt;code&gt;components.securitySchemes&lt;/code&gt;, which is the pattern to copy if your API uses JWT bearer auth. Security schemes matter downstream: documentation UIs use them to render an authentication form, and SDK generators use them to decide what the client constructor asks for.&lt;/p&gt;
&lt;p&gt;.NET 10 also lets transformers generate a schema for any C# type with &lt;code&gt;context.GetOrCreateSchemaAsync(...)&lt;/code&gt; and register it with &lt;code&gt;document.AddComponent(...)&lt;/code&gt;, which is handy for adding a shared &lt;code&gt;ProblemDetails&lt;/code&gt; error schema to every operation.&lt;/p&gt;
&lt;h3&gt;Upgrading transformers from .NET 9 to .NET 10&lt;/h3&gt;
&lt;p&gt;This is the one real migration cost. .NET 10 moves to version 2 of the &lt;code&gt;Microsoft.OpenApi&lt;/code&gt; object model. In practice:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;using Microsoft.OpenApi.Models;&lt;/code&gt; becomes &lt;code&gt;using Microsoft.OpenApi;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Collections such as &lt;code&gt;components.securitySchemes&lt;/code&gt; are typed by interface, for example &lt;code&gt;Dictionary&amp;#x3C;string, IOpenApiSecurityScheme&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;References use dedicated types such as &lt;code&gt;new OpenApiSecuritySchemeReference(&quot;Bearer&quot;, document)&lt;/code&gt; instead of an &lt;code&gt;OpenApiReference&lt;/code&gt; object on the scheme.&lt;/li&gt;
&lt;li&gt;Collections may be null, so initialise them (&lt;code&gt;operation.Responses ??= new OpenApiResponses();&lt;/code&gt;) before adding to them.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Compare the .NET 9 and .NET 10 versions of the &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-10.0&quot;&gt;customization page&lt;/a&gt; side by side when you port a transformer; the samples are the same code in both object models.&lt;/p&gt;
&lt;h2&gt;Generate several documents&lt;/h2&gt;
&lt;p&gt;One app can publish more than one document, for example a public and an internal API, or &lt;code&gt;v1&lt;/code&gt; and &lt;code&gt;v2&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;csharp&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;builder.Services.AddOpenApi(&quot;public&quot;);
builder.Services.AddOpenApi(&quot;internal&quot;, options =&gt;
{
    options.AddDocumentTransformer&amp;#x3C;BearerSecuritySchemeTransformer&gt;();
});

app.MapGet(&quot;/books&quot;, ListBooks).WithGroupName(&quot;public&quot;);
app.MapGet(&quot;/admin/audit&quot;, GetAuditLog).WithGroupName(&quot;internal&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each document is served at &lt;code&gt;/openapi/{name}.json&lt;/code&gt;. By default an endpoint lands in the document whose name matches its group name, and endpoints with no group name appear in every document. Override &lt;code&gt;ShouldInclude&lt;/code&gt; on the options if you need other rules. Each document gets its own transformers, so the internal one can carry auth requirements the public one does not.&lt;/p&gt;
&lt;h2&gt;Generate the document at build time&lt;/h2&gt;
&lt;p&gt;Serving the document from a running app is fine for development. For CI you usually want a file: something you can lint, diff against the last release, commit, and hand to other tools without booting the app. Add the build-time package:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;shell&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet add package Microsoft.Extensions.ApiDescription.Server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;dotnet build&lt;/code&gt; writes the document to the output directory. Two properties in the project file make it more useful:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;xml&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;PropertyGroup&gt;
  &amp;#x3C;OpenApiDocumentsDirectory&gt;.&amp;#x3C;/OpenApiDocumentsDirectory&gt;
  &amp;#x3C;OpenApiGenerateDocumentsOptions&gt;--file-name openapi&amp;#x3C;/OpenApiGenerateDocumentsOptions&gt;
&amp;#x3C;/PropertyGroup&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OpenApiDocumentsDirectory&lt;/code&gt; is relative to the project file, so &lt;code&gt;.&lt;/code&gt; writes next to your &lt;code&gt;.csproj&lt;/code&gt;. &lt;code&gt;OpenApiGenerateDocumentsOptions&lt;/code&gt; also accepts &lt;code&gt;--document-name v2&lt;/code&gt; to emit only one document, and &lt;code&gt;--openapi-version OpenApi3_1&lt;/code&gt; to pick the version at build time. YAML output at build time is not supported yet.&lt;/p&gt;
&lt;p&gt;A caution: build-time generation runs your app&apos;s startup code to discover endpoints. If &lt;code&gt;Program.cs&lt;/code&gt; connects to a database or reads secrets on startup, the build can fail on a clean agent. Microsoft&apos;s suggestion is to wrap that code in a check that &lt;code&gt;Assembly.GetEntryAssembly()?.GetName().Name&lt;/code&gt; is not &lt;code&gt;&quot;GetDocument.Insider&quot;&lt;/code&gt;, which is the entry assembly name during build-time generation.&lt;/p&gt;
&lt;h2&gt;Common mistakes&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Leaving operation IDs to chance.&lt;/strong&gt; Without &lt;code&gt;WithName&lt;/code&gt;, generated names are unstable, and every SDK built from the document inherits the instability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documenting responses the endpoint never declares.&lt;/strong&gt; XML &lt;code&gt;&amp;#x3C;response&gt;&lt;/code&gt; tags and descriptions do nothing without response metadata. Use typed results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using a variable for the document name&lt;/strong&gt; on .NET 10 and wondering where the XML comments went.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pinning OpenAPI 3.0 &quot;to be safe&quot;&lt;/strong&gt; and losing accurate nullability. Pin only for a consumer that needs it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serving the document publicly by accident.&lt;/strong&gt; If you remove the &lt;code&gt;IsDevelopment()&lt;/code&gt; guard, remember &lt;code&gt;MapOpenApi()&lt;/code&gt; returns a normal endpoint, so &lt;code&gt;.RequireAuthorization()&lt;/code&gt; and &lt;code&gt;.CacheOutput()&lt;/code&gt; work on it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never validating the output.&lt;/strong&gt; Run the generated file through a linter in CI. Our &lt;a href=&quot;https://scalar.com/learn/openapi/spectral-rules&quot;&gt;Spectral rules guide&lt;/a&gt; covers a sensible starting ruleset.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;What to do with the document&lt;/h2&gt;
&lt;p&gt;At this point you have a correct, versioned OpenAPI document coming out of your build. On its own it is a JSON file. The value comes from what reads it:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A documentation UI.&lt;/strong&gt; Microsoft&apos;s &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/using-openapi-documents&quot;&gt;using OpenAPI documents&lt;/a&gt; page shows Swagger UI, ReDoc and Scalar, and recommends enabling any of them only in development unless you mean to publish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client SDKs&lt;/strong&gt; generated from the document, in C# and the languages your users actually write.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; so AI agents can call the operations you choose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contract tests and breaking-change checks&lt;/strong&gt; that compare this build&apos;s document with the last release.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Part two of this series covers those in turn. If you want the short version for .NET today, the &lt;a href=&quot;https://scalar.com/docs-for/dotnet&quot;&gt;.NET API documentation&lt;/a&gt; page shows the two-line setup, and the &lt;a href=&quot;https://scalar.com/products/api-references/integrations/aspnetcore/integration&quot;&gt;ASP.NET Core integration guide&lt;/a&gt; lists every option.&lt;/p&gt;
&lt;h2&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Do I still need Swashbuckle in .NET 9 or .NET 10?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No. &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; generates the document. You only need a separate package for a UI, and you can choose any UI that reads OpenAPI. Existing Swashbuckle projects keep working if you are not ready to move.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which OpenAPI version does ASP.NET Core generate?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenAPI 3.0 on .NET 9 and OpenAPI 3.1 on .NET 10. Set &lt;code&gt;options.OpenApiVersion&lt;/code&gt; in &lt;code&gt;AddOpenApi&lt;/code&gt; to change it on .NET 10.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where is the OpenAPI document served?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At &lt;code&gt;/openapi/{documentName}.json&lt;/code&gt;, which is &lt;code&gt;/openapi/v1.json&lt;/code&gt; for the default document. Pass a different route to &lt;code&gt;MapOpenApi()&lt;/code&gt; to move it, and use a &lt;code&gt;.yaml&lt;/code&gt; suffix on .NET 10 for YAML.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why are my XML comments missing from the OpenAPI document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check three things: &lt;code&gt;GenerateDocumentationFile&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, you are on .NET 10, and the document name passed to &lt;code&gt;AddOpenApi&lt;/code&gt; is a string literal. Response descriptions also need the status code to be declared by the endpoint.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I add JWT bearer authentication to the document?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use a document transformer that adds an HTTP bearer scheme to &lt;code&gt;components.securitySchemes&lt;/code&gt;, and an operation transformer if you need to skip endpoints marked &lt;code&gt;[AllowAnonymous]&lt;/code&gt;. Microsoft&apos;s customization page has a complete example.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I generate the OpenAPI document without running the app?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. Add &lt;code&gt;Microsoft.Extensions.ApiDescription.Server&lt;/code&gt; and the document is written during &lt;code&gt;dotnet build&lt;/code&gt;. It still executes your startup code, so guard anything that needs external resources.&lt;/p&gt;
&lt;h2&gt;Related&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/learn/openapi/what-is-openapi&quot;&gt;What is OpenAPI?&lt;/a&gt; · &lt;a href=&quot;https://scalar.com/learn/openapi/openapi-3-1-vs-3-0&quot;&gt;OpenAPI 3.1 vs 3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/api-references/integrations/aspnetcore/integration&quot;&gt;ASP.NET Core integration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/docs-for/dotnet&quot;&gt;.NET API documentation&lt;/a&gt; — turn the document from this post into interactive docs with one NuGet package&lt;/li&gt;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>Migrating off Stainless: week-one checklist</title>
      <link>https://scalar.com/blog/posts/2026-10-01-migrating-off-stainless-week-one-checklist</link>
      <guid>https://scalar.com/blog/posts/2026-10-01-migrating-off-stainless-week-one-checklist</guid>
      <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;If your SDKs were generated by Stainless, the most useful thing you can do this week is not choosing a new generator. It is making sure that whatever you choose, you can prove your users&apos; code still works afterwards. This post is a five-day checklist for that first week: what to inventory, what to snapshot, which credentials to find, and which test to write before anyone regenerates anything.&lt;/p&gt;
&lt;p&gt;Some context, briefly. On 18 May 2026 Stainless &lt;a href=&quot;https://www.stainless.com/blog/stainless-is-joining-anthropic&quot;&gt;announced it is joining Anthropic&lt;/a&gt; and winding down its hosted products, including the SDK generator, with new signups, projects and SDKs closed the same day. The SDKs you already generated are yours. What stops is regeneration.&lt;/p&gt;
&lt;p&gt;We have written about this three times already, and this post deliberately does not repeat them:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://scalar.com/resources/stainless-wind-down&quot;&gt;The Stainless SDK generator wind-down&lt;/a&gt; is the full account: what changed, how to evaluate the options, and a key-by-key &lt;code&gt;stainless.yml&lt;/code&gt; mapping.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scalar.com/alternatives/stainless&quot;&gt;Stainless alternatives&lt;/a&gt; compares the generators you can move to, including where each one beats us.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scalar.com/resources/migration/stainless&quot;&gt;How to migrate from Stainless to Scalar&lt;/a&gt; is the step-by-step guide if you have already picked Scalar.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This one sits before all of them. Nothing on this list depends on which generator you pick, and most of it gets harder the longer you wait.&lt;/p&gt;
&lt;h2&gt;The week at a glance&lt;/h2&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Output you should have by the end of the day&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;One table listing every SDK, package, repository and owner&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Snapshot&lt;/td&gt;
&lt;td&gt;A committed record of each SDK&apos;s public surface, tagged&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Credentials and access&lt;/td&gt;
&lt;td&gt;A list of every token, app and workflow that publishes your SDKs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Compatibility harness&lt;/td&gt;
&lt;td&gt;A test suite that exercises every public method of the current SDK&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Guardrails and communication&lt;/td&gt;
&lt;td&gt;A CI check on API changes, a decision date, and a note for your team&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;You can compress this into two days on a small API or stretch it over two weeks on a large one. The order matters more than the pace.&lt;/p&gt;
&lt;h2&gt;Day 1: Inventory what you actually ship&lt;/h2&gt;
&lt;p&gt;Most teams know roughly which SDKs they have. Fewer can list, without looking, every package name in every registry, which repository each one is built from, and who last touched it. Start there, because every later step refers back to this table.&lt;/p&gt;
&lt;p&gt;Open your &lt;code&gt;stainless.yml&lt;/code&gt;. Its &lt;code&gt;targets&lt;/code&gt; block lists every language you generate. For each target, record:&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Where to find it&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Language and package name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;targets&lt;/code&gt; in &lt;code&gt;stainless.yml&lt;/code&gt;, then the registry page (npm, PyPI, Maven Central, RubyGems, Go module path)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production repository&lt;/td&gt;
&lt;td&gt;The repository your releases are published from&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Current published version&lt;/td&gt;
&lt;td&gt;The registry page&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Date of the last regeneration&lt;/td&gt;
&lt;td&gt;The most recent generated commit in the production repository&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Files with hand-written changes&lt;/td&gt;
&lt;td&gt;Commits in the repository that were not made by the generator&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;A named person, not a team alias&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Known heavy users&lt;/td&gt;
&lt;td&gt;Your largest customers or internal services that depend on the SDK&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;Two rows people forget. First, &lt;code&gt;targets: terraform&lt;/code&gt; and &lt;code&gt;targets: sql&lt;/code&gt;: if you generated either, flag them now, because they narrow your options more than anything else. Second, docs: if you used the Stainless Docs Platform, add a row for that repository too, and note that it lives in the &lt;code&gt;stainless-sdks&lt;/code&gt; GitHub organisation rather than yours.&lt;/p&gt;
&lt;p&gt;The &quot;files with hand-written changes&quot; column deserves real time. Stainless merged custom code into generated files, so your edits are interleaved with generated code rather than sitting in a separate folder. Run &lt;code&gt;git log&lt;/code&gt; on each production repository, filter out the generator&apos;s commits, and list every file a human changed. You will need this list on day 4 and again on the day you regenerate.&lt;/p&gt;
&lt;h2&gt;Day 2: Snapshot the public surface&lt;/h2&gt;
&lt;p&gt;Your public surface is every class, method, parameter and type your users can import. It is the thing a migration must not break, and today is the last day it is guaranteed to match what is installed in the wild. Record it now, in a form a machine can compare later.&lt;/p&gt;
&lt;p&gt;Start with what you already have. Stainless SDK repositories include an &lt;code&gt;api.md&lt;/code&gt; that lists every method grouped by resource. Copy it somewhere safe and tag the commit. That is your human-readable baseline.&lt;/p&gt;
&lt;p&gt;Then add a machine-readable one per language. These are the tools we would reach for; any equivalent works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# API Extractor writes a report of every exported declaration.
# Run init once, point it at your built .d.ts entry point, then run.
npx @microsoft/api-extractor init
npx @microsoft/api-extractor run --local
git add etc/*.api.md
git tag sdk-surface-baseline&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# Griffe dumps the public API and can check a later version against it.
pip install griffe
griffe dump your_package &gt; api-baseline.json
git tag sdk-surface-baseline
# Later: griffe check your_package --against sdk-surface-baseline&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# apidiff compares the exported API of two versions of a package.
go install golang.org/x/exp/cmd/apidiff@latest
apidiff -w baseline.export github.com/your-org/your-sdk-go
git tag sdk-surface-baseline&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Java, Kotlin, Ruby and the rest, the principle is the same: produce a file that lists every public symbol, commit it, and tag the commit. If there is no tool you trust, a script that imports the package and walks its exports is fine.&lt;/p&gt;
&lt;p&gt;Also capture the things a signature report misses:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;The client class name and constructor options.&lt;/li&gt;
&lt;li&gt;The environment variables the client reads for credentials and base URL.&lt;/li&gt;
&lt;li&gt;Default timeouts, retry counts and which status codes trigger a retry.&lt;/li&gt;
&lt;li&gt;How pagination is exposed: auto-iterating lists, page objects, cursors.&lt;/li&gt;
&lt;li&gt;Error class names and their hierarchy.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;These are behaviours users depend on without ever writing them down. Write them down now.&lt;/p&gt;
&lt;h2&gt;Day 3: Find every credential, app and workflow&lt;/h2&gt;
&lt;p&gt;Today is about control. When you move, you need to be able to publish new versions of the same packages from a pipeline you own. Find out now whether you can.&lt;/p&gt;
&lt;p&gt;Work through the inventory table and, for every package, answer:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Who owns the package in the registry?&lt;/strong&gt; On npm, &lt;code&gt;npm owner ls &amp;#x3C;package&gt;&lt;/code&gt; lists maintainers. On PyPI, check the project&apos;s collaborators and any trusted publishers. On Maven Central and RubyGems, check the namespace and gem owners. If the only owner is a person who has left, fix that this week.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What publishes it today?&lt;/strong&gt; Read every workflow in &lt;code&gt;.github/workflows&lt;/code&gt; in the production repository. Note each secret it references and where the secret is stored.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which GitHub Apps can write to the repository?&lt;/strong&gt; Check the organisation&apos;s installed apps and note what the Stainless app can access. Our migration guide covers removing it; for now, the goal is to know its scope and to make sure nothing breaks when it goes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is anything pointing back at Stainless infrastructure?&lt;/strong&gt; Search the repositories for &lt;code&gt;stainless&lt;/code&gt; in workflow files, scripts and READMEs.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The output is a short list: package, owner, publishing workflow, secret names, and any access that depends on Stainless. If a row has a gap, that is your first real task, and it is much easier to close while nothing is on fire.&lt;/p&gt;
&lt;h2&gt;Day 4: Build a compatibility harness&lt;/h2&gt;
&lt;p&gt;The snapshot from day 2 tells you if a signature changed. It does not tell you whether a user&apos;s code still runs. For that, you want a small test suite that uses the current SDK the way your users do, and that you can later point at any regenerated SDK unchanged.&lt;/p&gt;
&lt;p&gt;Keep it simple:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;One call per public method.&lt;/strong&gt; Walk &lt;code&gt;api.md&lt;/code&gt; and write a test that calls each method with realistic arguments. Assert on the shape of the result, not on exact values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run it against a mock server.&lt;/strong&gt; Your OpenAPI document already describes every response, so a mock server can answer these calls without touching production. The &lt;a href=&quot;https://scalar.com/tools/mock-server/getting-started&quot;&gt;Scalar mock server&lt;/a&gt; is one option; any OpenAPI mock server works.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cover the behaviours from day 2.&lt;/strong&gt; A test that pages through a list, a test that triggers a retry on a &lt;code&gt;429&lt;/code&gt;, a test that catches the typed error on a &lt;code&gt;404&lt;/code&gt;, and a test that constructs the client from environment variables alone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compile your README snippets.&lt;/strong&gt; Copy every code sample from your README and docs into the test suite. They are the code your users copied first, so they are the most likely to break.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Run the suite against the current published version and commit it green. When you evaluate a generator, you install its output in place of the current SDK and run the same suite. A failure is a breaking change you found before your users did.&lt;/p&gt;
&lt;p&gt;This is also where the day-1 list of hand-edited files pays off. Every hand-written helper should have at least one test in the harness, because it is the code most likely to be lost in regeneration.&lt;/p&gt;
&lt;h2&gt;Day 5: Put guardrails on the API and tell your team&lt;/h2&gt;
&lt;p&gt;Nothing breaks until your API changes. So the last step is to make sure the next API change cannot ship quietly while the SDKs are frozen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add a CI check on the OpenAPI document.&lt;/strong&gt; Diff the document on every pull request against the one your SDKs were last generated from. Any change to a path, parameter, or schema should fail the check, or at least require an explicit approval that says &quot;the SDK will not reflect this yet&quot;. This turns an invisible gap into a visible decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set a decision date.&lt;/strong&gt; The real deadline is not Stainless&apos;s; it is your next API change that users need in the SDK. Put a date on the calendar that is comfortably before it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write down how you will evaluate options.&lt;/strong&gt; The &lt;a href=&quot;https://scalar.com/resources/stainless-wind-down&quot;&gt;wind-down page&lt;/a&gt; has the long version. The short version is one question for every vendor: how does my existing public surface survive? You now have the tools to check the answer yourself: the snapshot from day 2 and the harness from day 4.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tell your team.&lt;/strong&gt; A short internal note is enough:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Our SDKs were generated by Stainless, which has stopped accepting new work. Published SDKs keep working. Until we move, SDK changes are frozen and API changes that affect the SDK need sign-off from [owner]. We have a surface snapshot and a compatibility test suite, and we will decide on a replacement by [date].&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;You do not need to tell customers anything yet, unless your API is about to change in a way the SDK must reflect. When you do migrate, the goal is that there is nothing to tell them: same package names, same imports, same method names, a normal minor version bump.&lt;/p&gt;
&lt;h2&gt;What not to do in week one&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Do not regenerate from the OpenAPI document alone into your main branch.&lt;/strong&gt; Any generator will produce a working SDK with a different public surface. Try it on a branch if you like, then run the day-4 harness and read the failures. It is an instructive afternoon.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not rename packages.&lt;/strong&gt; A new package name forces every user to change their install and their imports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not delete &lt;code&gt;stainless.yml&lt;/code&gt;.&lt;/strong&gt; It holds your resource tree, method names and pagination rules, and some generators, including ours, read it directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not remove the Stainless GitHub App before you can publish another way.&lt;/strong&gt; Remove it once the new pipeline has shipped a release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not let the freeze become permanent.&lt;/strong&gt; Every week of API changes you defer is another week of drift to reconcile later.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Where to go next&lt;/h2&gt;
&lt;p&gt;If you have finished the checklist, you are in a better position than most teams to evaluate options, because you can test the claims. Start with the &lt;a href=&quot;https://scalar.com/resources/stainless-wind-down&quot;&gt;wind-down write-up&lt;/a&gt; for the evaluation criteria, then &lt;a href=&quot;https://scalar.com/alternatives/stainless&quot;&gt;Stainless alternatives&lt;/a&gt; for the field. If you choose Scalar, the &lt;a href=&quot;https://scalar.com/resources/migration/stainless&quot;&gt;migration guide&lt;/a&gt; picks up exactly where this list ends: import your OpenAPI document and &lt;code&gt;stainless.yml&lt;/code&gt;, then diff the generated &lt;code&gt;api.md&lt;/code&gt; against the snapshot you took on day 2.&lt;/p&gt;
&lt;h2&gt;Frequently asked questions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Do my Stainless-generated SDKs stop working?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No. Published packages keep working and the code is yours. What stops is regenerating them when your API changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What should I do first when migrating off Stainless?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inventory every SDK you ship and snapshot its public surface. Both are independent of which generator you choose, and both are easier to do before anything changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I check that a new generator did not break my SDK?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Compare the new SDK&apos;s public surface with your snapshot, then run a compatibility test suite written against the current SDK. Signature diffs catch renamed methods; the tests catch changed behaviour such as pagination and retries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I keep the same package names after migrating?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes. The packages and registries are yours. Keeping the names is the most important thing you can do for your users, and any serious migration should support it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is there a deadline for leaving Stainless?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stainless&apos;s announcement does not give an end date for existing projects, and we had not seen one published when this post was written. Your practical deadline is your next API change that your SDK users need.&lt;/p&gt;
&lt;h2&gt;Related&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/learn/sdk/generate-sdk-from-openapi&quot;&gt;Generate an SDK from OpenAPI&lt;/a&gt; · &lt;a href=&quot;https://scalar.com/learn/sdk/build-vs-buy-sdk&quot;&gt;Build vs buy an SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/resources/migration/stainless&quot;&gt;How to migrate from Stainless to Scalar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/sdk-generator&quot;&gt;SDK generator&lt;/a&gt; — reads your &lt;code&gt;stainless.yml&lt;/code&gt; so your users&apos; call sites keep working&lt;/li&gt;&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Stainless facts in this post come from Stainless&apos;s own announcement and documentation, as of 26 September 2026. If Stainless publishes a timeline or anything here is out of date, please &lt;a href=&quot;https://github.com/scalar/scalar/issues&quot;&gt;open an issue&lt;/a&gt; and we will correct it.&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Release notes: 19–26 September 2026</title>
      <link>https://scalar.com/blog/posts/2026-09-29-release-notes-2026-09-26</link>
      <guid>https://scalar.com/blog/posts/2026-09-29-release-notes-2026-09-26</guid>
      <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;A big week for OpenAPI 3.2. The API reference and API client now understand &lt;code&gt;querystring&lt;/code&gt; parameters, custom HTTP methods through &lt;code&gt;additionalOperations&lt;/code&gt;, the new &lt;code&gt;dataValue&lt;/code&gt; and &lt;code&gt;serializedValue&lt;/code&gt; examples, streaming item schemas, and OAuth device authorization. There is also a new Copy as Markdown button, API client translations, live streaming response views, and a batch of fixes and hardening. A few changes need attention when you upgrade; they are collected at the end.&lt;/p&gt;
&lt;p&gt;Everything below comes from the changelogs of &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/packages/api-reference/CHANGELOG.md&quot;&gt;&lt;code&gt;@scalar/api-reference&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/packages/api-client/CHANGELOG.md&quot;&gt;&lt;code&gt;@scalar/api-client&lt;/code&gt;&lt;/a&gt; and the commit history of the monorepo for the week. Each item links to its pull request.&lt;/p&gt;
&lt;h2&gt;Versions released this week&lt;/h2&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Versions&lt;/th&gt;
&lt;th&gt;Release dates&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;@scalar/api-reference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.70.0, 1.71.0, 1.72.0, 1.72.1&lt;/td&gt;
&lt;td&gt;21, 22, 24 and 25 September&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@scalar/api-client&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3.19.3, 3.20.0, 3.21.0, 3.21.1&lt;/td&gt;
&lt;td&gt;21, 22, 24 and 25 September&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;The integrations (ASP.NET Core, FastAPI, NestJS, Hono and the rest) pick up API reference changes through their normal releases.&lt;/p&gt;
&lt;h2&gt;OpenAPI 3.2 support&lt;/h2&gt;
&lt;p&gt;OpenAPI 3.2 adds several features that did not have a home in 3.1. This week, a lot of them landed in both the reference and the client.&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;&lt;code&gt;in: querystring&lt;/code&gt; parameters.&lt;/strong&gt; A parameter can now describe the entire query string as one piece of content, for example a JSON object. The reference renders its schema, the client lets you edit the whole query string, and generated request URLs and code samples encode it correctly. Named query parameters keep their existing encoding when they sit alongside whole-query content. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10189&quot;&gt;#10189&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;additionalOperations&lt;/code&gt; and custom HTTP methods.&lt;/strong&gt; Operations using methods outside the standard set, such as &lt;code&gt;QUERY&lt;/code&gt; or &lt;code&gt;PROPFIND&lt;/code&gt;, now appear in navigation, documentation, callbacks and the client, with their original spelling preserved when displayed, sent and turned into code samples. The client shows a clear error for methods browsers refuse to send (&lt;code&gt;CONNECT&lt;/code&gt;, &lt;code&gt;TRACE&lt;/code&gt;, &lt;code&gt;TRACK&lt;/code&gt;), and request bodies are preserved for extension methods. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10186&quot;&gt;#10186&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dataValue&lt;/code&gt; and &lt;code&gt;serializedValue&lt;/code&gt; examples.&lt;/strong&gt; Named parameters use these new example fields in the editor, in outgoing requests and in code samples, without encoding an already serialized value twice. Optional parameters with one of these examples are now enabled by default, matching the older &lt;code&gt;value&lt;/code&gt; examples. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10322&quot;&gt;#10322&lt;/a&gt;) Example selection is shared across request bodies, response examples and snippets. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10208&quot;&gt;#10208&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming item schemas.&lt;/strong&gt; Request body examples and schema views support 3.2 streaming item schemas, and generated examples are framed as JSON Lines, JSON Sequence or server-sent events. cURL snippets use &lt;code&gt;--data-binary&lt;/code&gt; for streaming media types. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10178&quot;&gt;#10178&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OAuth device authorization.&lt;/strong&gt; The client supports the device authorization flow, with verification codes, cancellable token polling, stored credentials and OAuth metadata discovery. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10191&quot;&gt;#10191&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XML examples.&lt;/strong&gt; Examples are now generated from schema XML metadata, including attributes, namespaces, array wrappers and 3.2 text and CDATA nodes, and the same serialization is used for request bodies, code samples, response examples, mock responses and Markdown. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10192&quot;&gt;#10192&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Behind these, the workspace store and editor moved to OpenAPI 3.2 schemas while continuing to accept 3.1 documents. The editor offers 3.2 fields to documents that declare 3.1 as well; it does not certify that your document conforms to the version it declares. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10177&quot;&gt;#10177&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;New in the API reference&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Copy as Markdown.&lt;/strong&gt; Operations and webhooks have a Copy as Markdown button in both layouts, handy for pasting an endpoint into an issue, a pull request or a chat with an AI assistant. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10222&quot;&gt;#10222&lt;/a&gt;) It also respects document-defined additional operations. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10186&quot;&gt;#10186&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;expandAllParameters&lt;/code&gt; option.&lt;/strong&gt; Defaults to &lt;code&gt;true&lt;/code&gt;. Set it to &lt;code&gt;false&lt;/code&gt; to start operation parameters collapsed and expand each one on click. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10283&quot;&gt;#10283&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Searchable document selector.&lt;/strong&gt; The multi-document selector gained title filtering and keyboard-friendly search. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10269&quot;&gt;#10269&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discriminator values in schema selectors.&lt;/strong&gt; &lt;code&gt;oneOf&lt;/code&gt; and &lt;code&gt;anyOf&lt;/code&gt; selectors show discriminator mapping values next to schema names. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10317&quot;&gt;#10317&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response variant picker.&lt;/strong&gt; Generated response examples with &lt;code&gt;anyOf&lt;/code&gt; or &lt;code&gt;oneOf&lt;/code&gt; variants get a picker. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10203&quot;&gt;#10203&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response example descriptions.&lt;/strong&gt; Example summaries and descriptions are shown with their payloads. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10249&quot;&gt;#10249&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AsyncAPI payload examples&lt;/strong&gt; are generated from JSON Schema when no explicit example is provided. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10217&quot;&gt;#10217&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDK samples follow the example switcher.&lt;/strong&gt; SDK code samples can be linked to named request body examples. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10310&quot;&gt;#10310&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clearer authentication tooltips,&lt;/strong&gt; with separate headings, readable scheme types, and API key locations and names. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10335&quot;&gt;#10335&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;New in the API client&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Live streaming responses.&lt;/strong&gt; JSON Lines, JSON Sequences and multipart responses are displayed as they arrive instead of after the full response completes, with cancellation, malformed-record reporting and bounded display limits. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10207&quot;&gt;#10207&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translations.&lt;/strong&gt; The client UI, including the client embedded in the API reference, can be translated through &lt;code&gt;localization.translations.apiClient&lt;/code&gt;, and ships with English, Russian, Spanish, French, German, Simplified Chinese, Arabic and Portuguese. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10175&quot;&gt;#10175&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;The API client modal now loads the first time you open Test Request, not when the reference mounts, and shows loading feedback while it downloads. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10284&quot;&gt;#10284&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The agent drawer and chat load when first opened. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10287&quot;&gt;#10287&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;External examples load on demand when they are visible or when Test Request opens, instead of all at once while the document loads. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10240&quot;&gt;#10240&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Fixes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;API reference&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Nested object boundaries are preserved for properties composed from multiple &lt;code&gt;allOf&lt;/code&gt; schemas. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10336&quot;&gt;#10336&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enum&lt;/code&gt; values are intersected when merging &lt;code&gt;allOf&lt;/code&gt; schemas, so inherited properties show only allowed values. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10232&quot;&gt;#10232&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Deep-linked headings stay visible below sticky or fixed headers. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10253&quot;&gt;#10253&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Sidebar navigation no longer scrolls past operations and models in themes with decorative backgrounds. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10333&quot;&gt;#10333&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Server-rendered accordion headers, auth tables and the initial sidebar selection now hydrate correctly. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10313&quot;&gt;#10313&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Colored &lt;code&gt;x-badges&lt;/code&gt; pick black or white text from the background&apos;s lightness. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10300&quot;&gt;#10300&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;API client&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Long response bodies stay scrollable in narrow layouts. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10350&quot;&gt;#10350&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Optional form fields are included when you select files or edit values. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10334&quot;&gt;#10334&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Optional header, query and cookie rows with a pre-populated default or enum value are enabled automatically instead of being silently dropped. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10255&quot;&gt;#10255&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Typing a header, cookie or query parameter no longer creates duplicate rows or loses focus. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10281&quot;&gt;#10281&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Parameter edits are preserved when another parameter changes. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10303&quot;&gt;#10303&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Security hardening&lt;/h2&gt;
&lt;p&gt;Several fixes this week harden how documents and configuration are handled. We recommend upgrading.&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Configuration data embedded in inline scripts is escaped in client-side rendering. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10344&quot;&gt;#10344&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The workspace store prevents prototype pollution in nested object operations. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10342&quot;&gt;#10342&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ts-deepmerge&lt;/code&gt; was updated in the object utilities to prevent method overrides. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10338&quot;&gt;#10338&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The mock server restricts AsyncAPI external references. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10343&quot;&gt;#10343&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Validation bounds union scoring depth, and trailing-dot encoding runs in linear time. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10315&quot;&gt;#10315&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10341&quot;&gt;#10341&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Elsewhere in the monorepo&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;The OpenAPI upgrader gained a best-effort upgrade mode, preserves Swagger 2.0 &lt;code&gt;x-nullable&lt;/code&gt;, preserves semantics when upgrading to OpenAPI 3.2, and migrates tag groups to the 3.2 tag hierarchy. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10346&quot;&gt;#10346&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10306&quot;&gt;#10306&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10211&quot;&gt;#10211&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10276&quot;&gt;#10276&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The mock server supports streaming item schemas and document origins and exports. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10176&quot;&gt;#10176&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10286&quot;&gt;#10286&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;JavaScript snippets preserve nested arrays. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10305&quot;&gt;#10305&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;OpenAPI-to-Markdown renders each shared schema once per page and preserves examples and schema details. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10329&quot;&gt;#10329&lt;/a&gt;, &lt;a href=&quot;https://github.com/scalar/scalar/pull/10275&quot;&gt;#10275&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;This blog now has an RSS feed. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/9984&quot;&gt;#9984&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Upgrade notes&lt;/h2&gt;
&lt;p&gt;Most of this week is additive. Check these if they apply to you:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Sticky headers on embedding sites (1.72.1).&lt;/strong&gt; Scalar no longer discovers host page headers automatically when scrolling to content. If you embed the reference under your own fixed or sticky header, register it with the &lt;code&gt;data-scalar-scroll-header&lt;/code&gt; attribute, or reserve its height with &lt;code&gt;--scalar-custom-header-height&lt;/code&gt;. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10333&quot;&gt;#10333&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XML snapshots (1.72.1, 3.21.1).&lt;/strong&gt; Mock server XML responses now use the shared schema-aware serializer. If you snapshot XML responses in tests, expect them to change. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10192&quot;&gt;#10192&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom HTTP method types (1.72.0, 3.21.0).&lt;/strong&gt; Traversed operation methods use the exported &lt;code&gt;OperationMethod&lt;/code&gt; type, which accepts custom strings. If you consume these types, handle unknown methods; exhaustive checks over a fixed method set no longer apply. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10186&quot;&gt;#10186&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional parameters with examples (1.72.0, 3.21.0).&lt;/strong&gt; Optional parameters with &lt;code&gt;dataValue&lt;/code&gt; or &lt;code&gt;serializedValue&lt;/code&gt; examples are enabled by default, so they are sent unless the user unchecks them. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10322&quot;&gt;#10322&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workspace store import paths (1.71.0, 3.20.0).&lt;/strong&gt; The OpenAPI 3.1 loose-schema generator was removed from &lt;code&gt;@scalar/workspace-store&lt;/code&gt;. Import from &lt;code&gt;@scalar/workspace-store/schemas/v3.2/openapi/index&lt;/code&gt; and &lt;code&gt;.../v3.2/openapi/reference&lt;/code&gt; instead. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10177&quot;&gt;#10177&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edited request bodies (1.71.0, 3.20.0).&lt;/strong&gt; Editing a request body replaces an authored &lt;code&gt;externalValue&lt;/code&gt; URL with the edited inline value in the workspace and exported API description. (&lt;a href=&quot;https://github.com/scalar/scalar/pull/10208&quot;&gt;#10208&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Found a problem after upgrading? &lt;a href=&quot;https://github.com/scalar/scalar/issues&quot;&gt;Open an issue&lt;/a&gt; with the version and a minimal OpenAPI document, and we will take a look.&lt;/p&gt;
&lt;h2&gt;Related&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/learn/openapi/what-is-an-api-reference&quot;&gt;What is an API reference?&lt;/a&gt; · &lt;a href=&quot;https://scalar.com/learn/openapi/what-is-an-api-client&quot;&gt;What is an API client?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/api-references/configuration&quot;&gt;API reference configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product:&lt;/strong&gt; &lt;a href=&quot;https://scalar.com/products/api-client&quot;&gt;API client&lt;/a&gt; — try this week&apos;s OpenAPI 3.2 support against your own document&lt;/li&gt;&lt;/ul&gt;</description>
    </item>
    <item>
      <title>We wrote up the Stainless wind-down</title>
      <link>https://scalar.com/blog/posts/2026-08-16-stainless-wind-down</link>
      <guid>https://scalar.com/blog/posts/2026-08-16-stainless-wind-down</guid>
      <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;Stainless &lt;a href=&quot;https://www.stainless.com/blog/stainless-is-joining-anthropic&quot;&gt;announced on 18 May 2026&lt;/a&gt; that it is joining Anthropic and winding down all of its hosted products, including the SDK generator. New signups, projects, and SDKs closed the same day.&lt;/p&gt;
&lt;p&gt;Three months on, the thing we keep hearing from teams in that position is not &quot;which generator is best&quot;. It is a narrower and more awkward question: &lt;em&gt;my users have already written code against my SDK — how do I move without breaking them?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That question has a specific answer, and it is not the one most migration content gives. Your OpenAPI document was never the hard part of leaving Stainless. It is yours and every generator reads it. The hard part is &lt;code&gt;stainless.yml&lt;/code&gt; — the file that decides which operations become which namespaces, what each method is called, which pagination scheme each list endpoint uses, and what your package is named in every language. None of that is expressible in OpenAPI. Regenerate from the specification alone with any tool and you get a working SDK with a different public surface, which is a breaking change your users absorb.&lt;/p&gt;
&lt;p&gt;So we wrote a full account of the situation: &lt;a href=&quot;https://scalar.com/resources/stainless-wind-down&quot;&gt;&lt;strong&gt;The Stainless SDK generator wind-down&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It covers what Stainless actually said and what it did not say, what keeps working and what stops, and an assessment of the realistic options — OpenAPI Generator, Speakeasy, Fern, APIMatic, liblab, &lt;code&gt;stainful&lt;/code&gt;, &lt;code&gt;oagen&lt;/code&gt;, and Scalar — with the places each of them beats us stated plainly. Speakeasy generates Terraform providers and we do not. Fern&apos;s generators are Apache-2.0 and ours are not. OpenAPI Generator is free and cannot be wound down by anybody. If one of those is your requirement, the page says so.&lt;/p&gt;
&lt;p&gt;It also gets specific about the part we do well. Scalar reads &lt;code&gt;stainless.yml&lt;/code&gt; as an input, so your resource tree, method names, pagination schemes, and per-language package names carry across and your users&apos; call sites keep working. The page includes a key-by-key mapping between the two config formats, including the places where it does not carry — &lt;code&gt;targets: terraform&lt;/code&gt; and &lt;code&gt;targets: sql&lt;/code&gt; have no Scalar equivalent, and &lt;code&gt;openapi.transforms&lt;/code&gt; has to be applied upstream.&lt;/p&gt;
&lt;p&gt;There is a section at the end for people who are not choosing Scalar, because most of the migration work is the same whoever you pick, and some of it is worth doing this week regardless. Snapshot your current public API surface while you can still regenerate it. Find your hand-written code before you regenerate anything. Get your docs repository out of the &lt;code&gt;stainless-sdks&lt;/code&gt; organisation.&lt;/p&gt;
&lt;p&gt;If you are migrating and something on that page is wrong, unfair, or out of date — including if you work for one of the companies named on it — &lt;a href=&quot;https://github.com/scalar/scalar/issues&quot;&gt;open an issue&lt;/a&gt; and we will correct it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://scalar.com/resources/stainless-wind-down&quot;&gt;Read the full piece →&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Scalar&apos;s 2025 Open Source Pledge</title>
      <link>https://scalar.com/blog/posts/2026-04-11-oss-pledge</link>
      <guid>https://scalar.com/blog/posts/2026-04-11-oss-pledge</guid>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;What is Open Source Pledge?&lt;/p&gt;
&lt;p&gt;You can read more about it &lt;a href=&quot;https://opensourcepledge.com/&quot;&gt;here&lt;/a&gt;, but in short, it&apos;s paying Open Source maintainers. The minimum to participate is $2,000 per full-time employed developer per year.&lt;/p&gt;
&lt;p&gt;Open source is the foundation everything we build sits on. In &lt;strong&gt;2025&lt;/strong&gt;, Scalar is made up of &lt;strong&gt;10 developers&lt;/strong&gt; and contributed &lt;strong&gt;$21,232&lt;/strong&gt; directly to open source maintainers -- &lt;strong&gt;$2,123 per developer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We sponsored the following maintainers and projects:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/tiangolo&quot;&gt;tiangolo&lt;/a&gt; (FastAPI)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/SaltyAom&quot;&gt;SaltyAom&lt;/a&gt; (ElysiaJS)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hey-api&quot;&gt;hey-api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/litestar-org&quot;&gt;litestar-org&lt;/a&gt; (LiteStar)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/colinhacks&quot;&gt;colinhacks&lt;/a&gt; (Zod)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/daveshanley&quot;&gt;daveshanley&lt;/a&gt; (libopenapi)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mholt&quot;&gt;mholt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yusukebe&quot;&gt;yusukebe&lt;/a&gt; (Hono)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Aslemammad&quot;&gt;Aslemammad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pi0&quot;&gt;pi0&lt;/a&gt; (Nuxt / UnJS)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/typst&quot;&gt;typst&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MathurAditya724&quot;&gt;MathurAditya724&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fuma-nama&quot;&gt;fuma-nama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/thibaultleouay&quot;&gt;thibaultleouay&lt;/a&gt; (OpenStatus)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dmonad&quot;&gt;dmonad&lt;/a&gt; (Yjs)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/marijnh&quot;&gt;marijnh&lt;/a&gt; (CodeMirror / ProseMirror)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LinusBorg&quot;&gt;LinusBorg&lt;/a&gt; (Vue.js)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Most of us got into engineering through open source. We&apos;re glad to give back -- and we&apos;re proud to be members of the Open Source Pledge again in 2025.&lt;/p&gt;
&lt;p&gt;-- Marc, CEO &amp;#x26; co-founder, Scalar&lt;/p&gt;</description>
    </item>
    <item>
      <title>Too Long; Didn&apos;t Read; Used MCP</title>
      <link>https://scalar.com/blog/posts/2026-03-25-scalar-mcp-oauth</link>
      <guid>https://scalar.com/blog/posts/2026-03-25-scalar-mcp-oauth</guid>
      <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;It&apos;s 2026, who even READS the documentation for your API. Just boot up an MCP based on your OpenAPI document (using Scalar), and share it with your users or your team (agents).&lt;/p&gt;
&lt;p&gt;And then enhance any trillion dollar frontier model with your MCP server, that has all the context of how your API is defined. We&apos;ll show exactly how in the UI.&lt;/p&gt;
&lt;p&gt;If you want to add our Scalar Galaxy example, hack this into your terminal:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;bash&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx add-mcp https://mcp.scalar.com/mcp/67f954ca-123c-423b-b601-7284cfac3aff&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it? That&apos;s it. Open your thing and ask it something related to the API.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;claude, give me the curl for creating a new planet, buddy
make no mistakes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let it do its work…&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;⏺ Let me search the available APIs for a planet creation endpoint.

⏺ scalar-galaxy-mcp - search-openapi-operations (MCP)(question: &quot;create a new planet&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;… and you&apos;ll be amazed how accurate (and token-efficient and fast) it will reply:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Here&apos;s your curl:

curl -X POST https://galaxy.scalar.com/planets \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{ … }&apos;

Expects: 201 Created with the planet object (including a server-assigned id).

The only required field is name — everything else is optional.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(You can stop reading here, the following part is for all the great companies and enterprises using Scalar. Just open the dashboard and spin up your MCP in less than 60 seconds: &lt;a href=&quot;https://dashboard.scalar.com/&quot;&gt;https://dashboard.scalar.com&lt;/a&gt;, that&apos;s fine.)&lt;/p&gt;
&lt;p&gt;Sometimes, you don&apos;t want to share the docs publicly, no matter if it&apos;s in the browser or it&apos;s the MCP server. Your internal APIs, the new APIs you&apos;re about to release, the staging APIs… They are not for anyone. No worries, we got you.&lt;/p&gt;
&lt;p&gt;Set your MCP to &quot;private&quot; and share the URL with your team. They&apos;ll be able to authenticate using OAuth. This means their LLM will open a browser, where they can authenticate with Scalar to access your secret API (if they are on your team). Here&apos;s a video how easy it is:&lt;/p&gt;
&lt;p&gt;This is how documentation works for everyone (agents).&lt;/p&gt;</description>
    </item>
    <item>
      <title>Use your API in Cursor</title>
      <link>https://scalar.com/blog/posts/2026-03-17-agent-mcp</link>
      <guid>https://scalar.com/blog/posts/2026-03-17-agent-mcp</guid>
      <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;We&apos;ve been hard at work figuring out how to get Agent Scalar as close as possible to your favorite tools with as little overhead as possible, and here is what we came up with:&lt;/p&gt;
&lt;p&gt;Agent MCP Servers allow you to take your existing OpenAPI document and expose it via an MCP server with all the &lt;a href=&quot;https://scalar.com/blog/posts/2026-03-05-agent-scalar&quot;&gt;performance benefits&lt;/a&gt; of Agent:&lt;/p&gt;
&lt;h2&gt;Managing API Authentication&lt;/h2&gt;
&lt;p&gt;API authentication with AI tools is just a big headache. With Agent MCP servers you can preconfigure the authentication specified in your OpenAPI document so the LLM can operate the MCP tools without having to worry about how to fetch a token or navigate the auth structure.&lt;/p&gt;
&lt;h2&gt;MCP Server Privacy&lt;/h2&gt;
&lt;p&gt;By default, the access to the MCP servers you set up is restricted and requires a Scalar API Key in order to access and use the tools.&lt;/p&gt;
&lt;p&gt;Try it in the &lt;a href=&quot;https://dashboard.scalar.com/&quot;&gt;Scalar Dashboard&lt;/a&gt;. :-)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 17, 2026&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Introducing Agent</title>
      <link>https://scalar.com/blog/posts/2026-03-05-agent-scalar</link>
      <guid>https://scalar.com/blog/posts/2026-03-05-agent-scalar</guid>
      <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
      <description>&lt;figure&gt;&lt;img src=&quot;https://scalar.com/blog/agent-scalar.jpg&quot; alt=&quot;ASCII-style image of a headset&quot;&gt;&lt;/figure&gt;
&lt;p&gt;Making your AI Agents talk to APIs is easy and fun. It&apos;s just that bigger APIs are a worst-case workload for agents. Raw API definitions in the prompt overflows context so easily. I mean, we tried that. But all we got is hallucinated endpoints and unreliable responses.&lt;/p&gt;
&lt;p&gt;That&apos;s something MCP helps with. You just wrap your API as MCP tools, and you&apos;re good to go. That&apos;s what we thought. Actually, the repeated schemas come with a cost (tokens), too.&lt;/p&gt;
&lt;p&gt;I&apos;m glad to say, we found something better. It&apos;s called &lt;a href=&quot;https://scalar.com/products/agent/getting-started&quot;&gt;Agent&lt;/a&gt; and our newest product, tightly integrated with our set of tools for your API. And here is why it might be better than your average MCP server:&lt;/p&gt;
&lt;p&gt;Agent keeps the tool surface fixed and super small. It fetches just-in-time details. The result is smaller context, fewer steps, and better routing. And happy agents. :-)&lt;/p&gt;
&lt;h2&gt;Get Started&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://agent.scalar.com/&quot;&gt;
Chat with Agent
&lt;/a&gt;&lt;a href=&quot;https://dashboard.scalar.com/register&quot;&gt;
Create your own MCP&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;If you dump the full OpenAPI document into the prompt, you often blow past the model&apos;s context window before the model can do any work. That&apos;s exactly what happens with, for example, the &lt;a href=&quot;https://developers.zoom.us/docs/api/meetings/&quot;&gt;Zoom Meetings API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even when the API is smaller, like &lt;a href=&quot;https://developers.notion.com/guides/get-started/getting-started&quot;&gt;Notions API&lt;/a&gt;, raw OpenAPI is so expensive: It works, sure, but you pay a steep token tax every run. MCP reduces cost a lot, but native MCP still carries schema tokens for every single endpoint.&lt;/p&gt;
&lt;p&gt;Agent collapses that into three tools and pulls only the schema it needs.&lt;/p&gt;
&lt;h2&gt;Benchmarking what we built&lt;/h2&gt;
&lt;p&gt;We ran a few benchmarks to test Agent with real-world APIs. And the results are so good, but take a look yourself:&lt;/p&gt;
&lt;h3&gt;Benchmark setup&lt;/h3&gt;
&lt;p&gt;We ran identical tasks across three approaches:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Raw OpenAPI documents in prompt&lt;/li&gt;
&lt;li&gt;Native MCP server (one tool per endpoint)&lt;/li&gt;
&lt;li&gt;Agent (3 tools: summarize, search and execute)&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;We used &lt;a href=&quot;https://developers.zoom.us/docs/api/meetings/&quot;&gt;Zoom Meetings API&lt;/a&gt; (list, create, update) and &lt;a href=&quot;https://developers.notion.com/guides/get-started/getting-started&quot;&gt;Notions API&lt;/a&gt; (search, create page, get workspace). Example Notion prompts are aligned with Notion&apos;s MCP tools guide.&lt;/p&gt;
&lt;p&gt;Token counting was done with &lt;a href=&quot;https://github.com/openai/tiktoken&quot;&gt;tiktoken&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Zoom Meetings&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;th&gt;Success&lt;/th&gt;
&lt;th&gt;Avg Tokens&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;th&gt;Avg Steps&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;List upcoming meetings&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;385797&lt;/td&gt;
&lt;td&gt;2205 ms&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;List upcoming meetings&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;20179&lt;/td&gt;
&lt;td&gt;47887 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;List upcoming meetings&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;5531&lt;/td&gt;
&lt;td&gt;23029 ms&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;Create a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;385791&lt;/td&gt;
&lt;td&gt;2011 ms&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;Create a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;95474&lt;/td&gt;
&lt;td&gt;46529 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;Create a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;39327&lt;/td&gt;
&lt;td&gt;20637 ms&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;Update a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;385792&lt;/td&gt;
&lt;td&gt;1978 ms&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;Update a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;95406&lt;/td&gt;
&lt;td&gt;45189 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;Update a meeting&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;12674&lt;/td&gt;
&lt;td&gt;13367 ms&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Schema Cost (200k context)&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Token cost&lt;/th&gt;
&lt;th&gt;Schema Tokens&lt;/th&gt;
&lt;th&gt;All-in Tokens&lt;/th&gt;
&lt;th&gt;Context used (200k)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Raw OpenAPI Spec in prompt&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;295656&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;295656&lt;/td&gt;
&lt;td&gt;147.8%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Native MCP (full schemas)&lt;/td&gt;
&lt;td&gt;183&lt;/td&gt;
&lt;td&gt;89281&lt;/td&gt;
&lt;td&gt;89530&lt;/td&gt;
&lt;td&gt;178811&lt;/td&gt;
&lt;td&gt;89.4%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Native MCP (required params only)&lt;/td&gt;
&lt;td&gt;183&lt;/td&gt;
&lt;td&gt;5504&lt;/td&gt;
&lt;td&gt;89530&lt;/td&gt;
&lt;td&gt;95034&lt;/td&gt;
&lt;td&gt;47.5%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Agent (MCP tools)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h3&gt;Notion&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;th&gt;Success&lt;/th&gt;
&lt;th&gt;Avg Tokens&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;th&gt;Avg Steps&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;Search for budget approval docs&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;95819&lt;/td&gt;
&lt;td&gt;114858 ms&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;Search for budget approval docs&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14725&lt;/td&gt;
&lt;td&gt;39663 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;Search for budget approval docs&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;1873&lt;/td&gt;
&lt;td&gt;51474 ms&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;Create project kickoff page&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;96479&lt;/td&gt;
&lt;td&gt;75803 ms&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;Create project kickoff page&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14643&lt;/td&gt;
&lt;td&gt;51163 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;Create project kickoff page&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;1454&lt;/td&gt;
&lt;td&gt;103488 ms&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;raw-openapi&lt;/td&gt;
&lt;td&gt;Which workspace am I connected to?&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;95490&lt;/td&gt;
&lt;td&gt;22175 ms&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;native-mcp&lt;/td&gt;
&lt;td&gt;Which workspace am I connected to?&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14385&lt;/td&gt;
&lt;td&gt;27013 ms&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;agent-scalar&lt;/td&gt;
&lt;td&gt;Which workspace am I connected to?&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;1206&lt;/td&gt;
&lt;td&gt;27580 ms&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Schema Cost (200k context)&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Token cost&lt;/th&gt;
&lt;th&gt;Schema Tokens&lt;/th&gt;
&lt;th&gt;All-in Tokens&lt;/th&gt;
&lt;th&gt;Context used (200k)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Raw OpenAPI Spec in prompt&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;69114&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;69114&lt;/td&gt;
&lt;td&gt;34.6%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Native MCP (full schemas)&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;2104&lt;/td&gt;
&lt;td&gt;12803&lt;/td&gt;
&lt;td&gt;14907&lt;/td&gt;
&lt;td&gt;7.5%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Native MCP (required params only)&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;829&lt;/td&gt;
&lt;td&gt;12803&lt;/td&gt;
&lt;td&gt;13632&lt;/td&gt;
&lt;td&gt;6.8%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Agent (MCP tools)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;h2&gt;The Results&lt;/h2&gt;
&lt;p&gt;Guess who&apos;s the clear winner with just 0.2% of your context window: Agent. Why is that?&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;p&gt;Native MCP scales with endpoint count. Agent does not. Three tools cover the entire API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instead of loading the full API definition upfront, the agent calls a tiny version, with just the endpoints and schemas it needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The schema footprint is tiny (hundreds of tokens) even for large APIs like the Zoom Meetings API.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;ol&gt;&lt;li&gt;Upload your OpenAPI document to Scalar.&lt;/li&gt;
&lt;li&gt;Scalar augments it for search and execution.&lt;/li&gt;
&lt;li&gt;Agents connect via MCP with three tools:
    &lt;ul&gt;&lt;li&gt;&lt;code&gt;summarize-openapi-specs&lt;/code&gt; (short summary of specs and available endpoints)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search-openapi-operations&lt;/code&gt; (minified OpenAPI documents for the endpoints matching the user&apos;s search), and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execute-request&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2&gt;Try it&lt;/h2&gt;
&lt;p&gt;You can use Agent in two ways:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Chat UI:&lt;/strong&gt; upload your OpenAPI and chat at &lt;a href=&quot;https://agent.scalar.com/&quot;&gt;&lt;code&gt;agent.scalar.com&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent SDK:&lt;/strong&gt; connect to Scalar MCP servers from your agent runtime (Vercel AI SDK, OpenAI Agents SDK, Anthropic Claude SDK).&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;ts&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { Agent, MCPServerStreamableHttp, run } from &apos;@openai/agents&apos;
import { agentScalar } from &apos;@scalar-org/agent-sdk&apos;

const scalar = agentScalar({
  agentKey: &apos;YOUR_AGENT_KEY&apos;,
})

const session = await scalar.session()

const serverOptions = session.createOpenAIMCPServerOptions()
const servers = serverOptions.map((opts) =&gt; new MCPServerStreamableHttp(opts))
await Promise.all(servers.map((s) =&gt; s.connect()))

const agent = new Agent({
  name: &apos;api-agent&apos;,
  instructions: &apos;You help users interact with APIs.&apos;,
  mcpServers: servers,
})

const result = await run(agent, &apos;pls list available endpoints in the zoom api thanks&apos;)

await Promise.all(servers.map((s) =&gt; s.close()))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agent is able to scale across N APIs that you want your agent to have access to, without flooding the context window and yielding the most accurate tool calling. Crazy, eh?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 5, 2026&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to set up an OpenAPI mock server</title>
      <link>https://scalar.com/blog/posts/2025-08-19-how-to-set-up-an-openapi-mock-server</link>
      <guid>https://scalar.com/blog/posts/2025-08-19-how-to-set-up-an-openapi-mock-server</guid>
      <pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;To create a mock server for your OpenAPI document, download the &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md&quot;&gt;Scalar CLI&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm -g install @scalar/cli&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There’s another &lt;code&gt;scalar&lt;/code&gt; 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm -g --force install @scalar/cli&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Once installed, run the &lt;code&gt;document mock&lt;/code&gt; command pointing at your OpenAPI document (no matter if it’s JSON or YAML, local or remote).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scalar document mock https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/3.1.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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’ll see a color-coded output of available paths for you to make requests to:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When you make those requests, you’ll get responses as if that server exists:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The mock server also has a few options to customize it:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;--port&lt;/code&gt; to change the port from the default of &lt;code&gt;3000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--watch&lt;/code&gt; to watch for file changes and reload when you modify the OpenAPI file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--once&lt;/code&gt; to run tests once. Boots the server, responds to requests, and exits. Perfect for CI pipelines.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Why does OpenAPI mocking matter anyways?&lt;/h2&gt;
&lt;p&gt;Mock servers are more than just “fake APIs.” They unlock workflows that speed up development and reduce dependencies.&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Parallel development:&lt;/strong&gt; Frontend and backend teams can work independently without needing to wait for the real API to be built.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing without risk:&lt;/strong&gt; Safely run tests and automations against the mock server without hitting production or staging (and the real errors that come with them).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster prototyping:&lt;/strong&gt; Build and validate API contracts early in the process, before you’ve written any business logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer third-party dependencies:&lt;/strong&gt; Developers can work with third-party APIs without their rate limits, costs, and availability constraints.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;A good mock server gives you confidence in the API while keeping the whole team moving.&lt;/p&gt;
&lt;h2&gt;How does Scalar’s OpenAPI mock server work under the hood?&lt;/h2&gt;
&lt;p&gt;Under the hood, the CLI relies on Scalar’s open source &lt;code&gt;mock-server&lt;/code&gt; &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/mock-server&quot;&gt;package&lt;/a&gt; to generate routes and serve requests.&lt;/p&gt;
&lt;p&gt;The whole request flow looks like this:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;The Scalar CLI detects whether your input is a file or URL then loads, dereferences, and &lt;a href=&quot;https://scalar.com/blog/posts/2025-07-07-how-to-do-openapi-validation-and&quot;&gt;validates&lt;/a&gt; it with the &lt;code&gt;@scalar/openapi-parser&lt;/code&gt; &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/openapi-parser&quot;&gt;package&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With the OpenAPI doc, the &lt;code&gt;mock-server&lt;/code&gt; package generates routes for each path in Hono syntax and responses based on schemas. It also validates parameters, sets up authentication (if &lt;a href=&quot;https://scalar.com/blog/posts/2025-03-26-a-guide-to-openapi-security-and-how&quot;&gt;security schemes&lt;/a&gt; are defined), and returns appropriate HTTP status codes.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;The mock responses use Scalar’s open source &lt;code&gt;oas-utils&lt;/code&gt; &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/oas-utils&quot;&gt;package&lt;/a&gt; to generate realistic data from OpenAPI schemas. This uses examples when available.&lt;/li&gt;
&lt;li&gt;The generated data respects schema constraints like data types, formats, and validation rules to create mock responses that match your API specification.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;All this gives you a fast, spec-compliant, developer-friendly mock server you can start in seconds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Aug 19, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to do OpenAPI validation (and why it matters)</title>
      <link>https://scalar.com/blog/posts/2025-07-07-how-to-do-openapi-validation-and</link>
      <guid>https://scalar.com/blog/posts/2025-07-07-how-to-do-openapi-validation-and</guid>
      <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;To validate your OpenAPI document, download the &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md&quot;&gt;Scalar CLI&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm -g install @scalar/cli&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There’s another &lt;code&gt;scalar&lt;/code&gt; 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm -g --force install @scalar/cli&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;Once installed, run the &lt;code&gt;document validate&lt;/code&gt; command pointed at your OpenAPI document (no matter if it’s &lt;code&gt;JSON&lt;/code&gt; or &lt;code&gt;YAML&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scalar document validate galaxy.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have a validated OpenAPI document you can safely use however you like (or one that needs fixing 😅).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Fun fact:&lt;/strong&gt; Scalar’s &lt;a href=&quot;https://docs.scalar.com/&quot;&gt;OpenAPI editor&lt;/a&gt; has validation built-in. No command line required.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2&gt;Why does OpenAPI validation matter anyways?&lt;/h2&gt;
&lt;p&gt;Not sure why you’d do the above? You’ve come to the right place. The benefits of validating your OpenAPI doc include:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Prevents runtime errors and bad devex&lt;/strong&gt;. Invalid or incomplete docs can result in confused users, broken test environments, incorrect references, and frustrated devs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensures standards compliance.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enables automation.&lt;/strong&gt; Automatically validating your OpenAPI doc unlocks automation elsewhere. You won’t need to manually upload the doc to generate mock servers, tests, codegen, and related areas of your CI/CD pipeline. All of it &lt;a href=&quot;https://www.youtube.com/watch?v=nVqcxarP9J4&amp;#x26;pp=0gcJCfwAo7VqN5tD&quot;&gt;just works&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Catches breaking changes.&lt;/strong&gt; If you have internal API standards, validating an OpenAPI doc against them can ensure these standards aren’t broken. This can be critical for proper API governance.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;How does Scalar’s OpenAPI validation work?&lt;/h2&gt;
&lt;p&gt;Can’t get enough about OpenAPI validation, huh? Well here’s how it works under the hood.&lt;/p&gt;
&lt;p&gt;We love two things at Scalar: open source and OpenAPI. This means we’ve built a bunch of tools to work with OpenAPI and they are all open source.&lt;/p&gt;
&lt;p&gt;Most important for validation is the &lt;code&gt;openapi-parser&lt;/code&gt; &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/openapi-parser&quot;&gt;package&lt;/a&gt;. It is what the CLI is relying on to actually do the validation. Once you run the command, the process looks like this:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;The parser loads the doc and ensures all &lt;code&gt;$ref&lt;/code&gt; 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.&lt;/li&gt;
&lt;li&gt;It then checks the version then uses AJV (Another JSON Validator) to check against the official &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/openapi-parser/src/schemas&quot;&gt;OpenAPI JSON schema&lt;/a&gt;. There is specific validation that happens for different versions of OpenAPI.&lt;/li&gt;
&lt;li&gt;Once done, the CLI either returns a nice success message or transforms the AJV errors into a user-readable one.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;All this is packaged into a simple to use CLI (with &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/guides/cli/getting-started.md&quot;&gt;much more functionality&lt;/a&gt;). Also, because the &lt;code&gt;openapi-parser&lt;/code&gt; is &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/openapi-parser&quot;&gt;open source&lt;/a&gt;, if you wanted to implement (or modify) validation yourself, you could!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jul 7, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How we created an animated, responsive README</title>
      <link>https://scalar.com/blog/posts/2025-05-28-how-we-created-an-animated-responsive</link>
      <guid>https://scalar.com/blog/posts/2025-05-28-how-we-created-an-animated-responsive</guid>
      <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;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).&lt;/p&gt;
&lt;p&gt;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, &lt;a href=&quot;https://github.github.com/gfm/&quot;&gt;GitHub’s flavor of Markdown&lt;/a&gt; provides a lot more than people realize (like a lot of HTML being supported).&lt;/p&gt;
&lt;p&gt;At Scalar, we take full advantage of this to create responsive and animated elements that make &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/README.md&quot;&gt;our README&lt;/a&gt; pop. Here’s all the details:&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Creating responsive images&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The Scalar app is responsive, why shouldn’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.&lt;/p&gt;
&lt;p&gt;Normal Markdown images only enable you to put one size. Even if we used the &lt;code&gt;&amp;#x3C;img&gt;&lt;/code&gt; HTML component, we’d still be in the same boat. SVGs on the other hand, enable us to do cool stuff like this:&lt;/p&gt;
&lt;p&gt;This works by being an SVG with a &lt;code&gt;&amp;#x3C;foreignObject&gt;&lt;/code&gt; inside of it and &lt;code&gt;&amp;#x3C;html&gt;&lt;/code&gt; 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;100%&quot; height=&quot;100%&quot; viewBox=&quot;0 0 500 500&quot;&gt;
  &amp;#x3C;foreignObject width=&quot;100%&quot; height=&quot;100%&quot;&gt;
    &amp;#x3C;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; style=&quot;display: flex; align-items: center; justify-content: center; height: 100%;&quot;&gt;
      &amp;#x3C;img src=&quot;desktop-image.jpg&quot; style=&quot;width: 100%; max-width: 400px;&quot;&gt;
      &amp;#x3C;img src=&quot;mobile-image.jpg&quot; style=&quot;width: 100%; max-width: 400px; display: none;&quot;&gt;
      &amp;#x3C;style&gt;
        @media (max-width: 450px) {
          img:first-of-type { display: none; }
          img:last-of-type { display: block; }
        }
      &amp;#x3C;/style&gt;
    &amp;#x3C;/div&gt;
  &amp;#x3C;/foreignObject&gt;
&amp;#x3C;/svg&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we didn’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’ll probably not want to blind them with your images in the mode they choose.&lt;/p&gt;
&lt;p&gt;Luckily, GitHub makes this a lot easier than responsive images. You simply need to add &lt;code&gt;#gh-light-mode-only&lt;/code&gt; and &lt;code&gt;#gh-dark-mode-only&lt;/code&gt; tags to add to the images like we do here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;	&amp;#x3C;img width=&quot;830&quot; height=&quot;520&quot; src=&quot;https://github.com/user-attachments/assets/7c4d4971-a6d9-457d-a7ab-11894889f6f9#gh-light-mode-only&quot;&gt;
	&amp;#x3C;img width=&quot;830&quot; height=&quot;520&quot; src=&quot;https://github.com/user-attachments/assets/0e3ffca5-8912-487a-a390-fef0e8222c35#gh-dark-mode-only&quot;&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;Adding Real ASCII in real Markdown&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As a tool built for developers, we’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’t handle it well.&lt;/p&gt;
&lt;p&gt;Instead, we use SVGs with foreign objects (again). This lets us do this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;ASCII is cool&quot; title=&quot;ASCII is cool&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, this enables three things:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Use ASCII in our README&lt;/li&gt;
&lt;li&gt;Have a horizontal split for our two different products&lt;/li&gt;
&lt;li&gt;Have both link to the correct spots&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;This is done with a combination of HTML (for the links) and SVGs that look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;svg xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &amp;#x3C;foreignObject width=&quot;100%&quot; height=&quot;100%&quot;&gt;&amp;#x3C;script xmlns=&quot;&quot;/&gt;
        &amp;#x3C;div xmlns=&quot;http://www.w3.org/1999/xhtml&quot; class=&quot;fixme&quot;&gt;
            &amp;#x3C;div class=&quot;markdown-body&quot;&gt;
                &amp;#x3C;pre class=&quot;markdown-code&quot;&gt;

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

                .markdown-code {
                    font-family: ui-monospace, SFMono-Regular, &quot;SF Mono&quot;, Menlo, Consolas, &quot;Liberation Mono&quot;, monospace;
                    white-space: pre;
                    font-size: 0.5rem;
                }
                
                &amp;#x3C;!-- more styles... --&gt;
            &amp;#x3C;/style&gt;
        &amp;#x3C;/div&gt;
    &amp;#x3C;/foreignObject&gt;
&amp;#x3C;script xmlns=&quot;&quot;/&gt;&amp;#x3C;/svg&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;Creating animations that will knock your socks off&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Now, all this is cool so far, but you could mistake it for any other GitHub repo. Something that would be totally unique to us is some sweet, sweet animations running in our GitHub repo.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;foreignobjects&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our first iteration of this was &lt;a href=&quot;https://github.com/scalar/scalar/pull/3318&quot;&gt;a pillar in a room with each of our contributors projected on it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This works by:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Creating “walls” using background textures and rotations.&lt;/li&gt;
&lt;li&gt;Defining a 3D, rotating cube with ASCII-style avatars with commit info.&lt;/li&gt;
&lt;li&gt;More ASCII art with usernames, number of commits, and line additions/deletions.&lt;/li&gt;
&lt;li&gt;A CSS scroll animation to scroll this ASCII art continually upward&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Eventually, we got bored of this and moved airplanes. The airplanes are HTML and they are animated with CSS.&lt;/p&gt;
&lt;p&gt;This works by:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Creating two banners with the contributor name, commit count, and stats as well as the ASCII art of an airplane.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;@keyframes&lt;/code&gt;, they move from left to right continuously at slightly different offset speeds.&lt;/li&gt;
&lt;li&gt;The plane bounces continuously by repeatedly fading in and out of slightly different planes.&lt;/li&gt;
&lt;li&gt;The banner contributor text and color change based on &lt;code&gt;@keyframes&lt;/code&gt; as well.&lt;/li&gt;&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Why not use a gif?&lt;/strong&gt; 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?&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2&gt;&lt;strong&gt;Why all this work to make our README look cool?&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;&lt;li&gt;It’s fun.&lt;/li&gt;
&lt;li&gt;It makes us stand out.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;May 28, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How Scalar themes work</title>
      <link>https://scalar.com/blog/posts/2025-05-07-how-scalar-themes-work</link>
      <guid>https://scalar.com/blog/posts/2025-05-07-how-scalar-themes-work</guid>
      <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;Every product can be thought of as a combination of three things: data, functionality, and display.&lt;/p&gt;
&lt;p&gt;The beauty of &lt;a href=&quot;https://docs.scalar.com/&quot;&gt;API references&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;What’s not similar or standardized between products is &lt;strong&gt;display&lt;/strong&gt;. Design matters a lot. You don’t want people thinking they’ve entered another product when they go to your API reference. Ideally, you want them to think you built all the functionality yourself.&lt;/p&gt;
&lt;p&gt;We built &lt;strong&gt;themes&lt;/strong&gt; 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.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Enabling smooth integrations through customization&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way to customize your API reference is to use a pre-built theme. These include options like &lt;code&gt;moon&lt;/code&gt;, &lt;code&gt;solarized&lt;/code&gt;, &lt;code&gt;saturn&lt;/code&gt;, &lt;code&gt;mars&lt;/code&gt;, and more.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Web editor themes&quot; title=&quot;Web editor themes&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We’ve worked hard to make these options you want to use. For example, we’ve built a custom shadow system to work well in both light and dark modes.&lt;/p&gt;
&lt;p&gt;Normal shadow systems use dark shadows on light backgrounds to create depth, but those same shadows look wrong on dark backgrounds.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If you want to take the customization further, you can then dive into the CSS they are built with. These include &lt;code&gt;light-mode&lt;/code&gt; and &lt;code&gt;dark-mode&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;For example, changing the font requires you to set the &lt;code&gt;withDefaultFonts&lt;/code&gt; config option to &lt;code&gt;false&lt;/code&gt; and then set it with the &lt;code&gt;--scalar-font&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;!doctype html&gt;
&amp;#x3C;html&gt;
  &amp;#x3C;head&gt;
    &amp;#x3C;!-- Link to the font on Google --&gt;
    &amp;#x3C;link
      href=&quot;https://fonts.googleapis.com/css2?family=Roboto&quot;
      rel=&quot;stylesheet&quot; /&gt;
    &amp;#x3C;!-- Overwrite the Scalar font variable --&gt;
    &amp;#x3C;style&gt;
      :root {
        --scalar-font: &apos;Roboto&apos;, sans-serif;
      }
    &amp;#x3C;/style&gt;
  &amp;#x3C;/head&gt;
  &amp;#x3C;body&gt;
    &amp;#x3C;!-- Pass the custom configuration object --&gt;
    &amp;#x3C;script&gt;
      var configuration = {
        theme: &apos;kepler&apos;,
        withDefaultFonts: &apos;false&apos;,
      }
    &amp;#x3C;/script&gt;
    &amp;#x3C;script src=&quot;https://cdn.jsdelivr.net/npm/@scalar/api-reference&quot;&gt;&amp;#x3C;/script&gt;
  &amp;#x3C;/body&gt;
&amp;#x3C;/html&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All this gives you the options to customize Scalar to your brand and integrate it stylistically.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Making customization easy for developers&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;All variables:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Start with &lt;code&gt;--scalar-&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Are grouped logically by function (color, typography, layout)&lt;/li&gt;
&lt;li&gt;Clearly separated between light and dark mode&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Theme variables&quot; title=&quot;Theme variables&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Beyond this, we also use a layer system to create conflict-free styling. The first layer is &lt;code&gt;scalar-base&lt;/code&gt; which contains the core resets that normalize browser default styles and then sets the default styles for Scalar.&lt;/p&gt;
&lt;p&gt;The second layer is &lt;code&gt;scalar-theme&lt;/code&gt; which contains theme-specific styles and overrides. These are largely the variables you edit when you want to customize your theme.&lt;/p&gt;
&lt;p&gt;The last piece of theme’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.&lt;/p&gt;
&lt;p&gt;Oh, and we know it wouldn’t be good developer experience without a load of &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/themes.md&quot;&gt;documentation&lt;/a&gt; and code examples for all things themes. We hope all this makes themes not only easy to customize, but enjoyable too.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Making sure themes don’t break anything&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The last thing we want Scalar to do is break your app in unexpected ways.&lt;/p&gt;
&lt;p&gt;Similar to the other two sections, this starts by scoping well. For themes, everything is scoped under the &lt;code&gt;scalar-app&lt;/code&gt; class using the &lt;code&gt;:where&lt;/code&gt; scoping. This is the first layer of defence to prevent unintended style leakage. The &lt;code&gt;scalar-base&lt;/code&gt; and &lt;code&gt;scalar-theme&lt;/code&gt; layers provide another and the &lt;code&gt;light-mode&lt;/code&gt; and &lt;code&gt;dark-mode&lt;/code&gt; one after that.&lt;/p&gt;
&lt;p&gt;Another area that is liable to break is scrollbars. Unfortunately, macOS, Windows, and Linux all handle scrollbars differently. The ones on macOS don’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.&lt;/p&gt;
&lt;p&gt;To prevent this, we built a detection utility for obtrusive scrollbars that forces them to always be visible. It looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export function hasObtrusiveScrollbars(): boolean {
  if (typeof window === &apos;undefined&apos;) return false;

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

  // Create a 40px tall child that forces scrolling
  const child = document.createElement(&apos;div&apos;)
  child.setAttribute(&apos;style&apos;, &apos;width:100%;height:40px&apos;)
  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
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All of this helps developers gain confidence that Scalar will integrate with their existing setup and not break anything.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Themes are a core part of what makes Scalar work&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;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’d want without your users realizing you didn’t build it.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;May 7, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How Cloudinary’s API docs create a great developer experience</title>
      <link>https://scalar.com/blog/posts/2025-04-28-how-cloudinarys-api-docs-create-a</link>
      <guid>https://scalar.com/blog/posts/2025-04-28-how-cloudinarys-api-docs-create-a</guid>
      <pubDate>Mon, 28 Apr 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;The first line on Cloudinary’s homepage is “Image and Video API Platform.” &lt;strong&gt;API&lt;/strong&gt; is front and center as the primary way users interact with their app.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Giving users multiple ways to get started&lt;/h2&gt;
&lt;p&gt;Every app cares about getting users onboarded and activated, and Cloudinary is no exception.&lt;/p&gt;
&lt;p&gt;Cloudinary’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.&lt;/p&gt;
&lt;p&gt;The full range of this getting started content which includes:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Quick starts:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Videos:&lt;/strong&gt; Interactive video walkthroughs showing the core Cloudinary flows like how to upload assets, optimize images, cropping and resizing, and more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Samples and examples:&lt;/strong&gt; Examples with real, modifiable code you can test Cloudinary with. This provides users with an example they can edit to fit their use case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactivity:&lt;/strong&gt; 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.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Supporting a diversity of use cases with OpenAPI&lt;/h2&gt;
&lt;p&gt;Although the getting started content is great, as users move beyond basic use cases, they fragment and become complex, but still require support.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://scalar.com/&quot;&gt;Scalar&lt;/a&gt;. OpenAPI is a structured standard for describing APIs and Scalar uses it to display an API reference.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The great part about this combination is that OpenAPI is autogenerated, meaning Cloudinary doesn’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.&lt;/p&gt;
&lt;p&gt;This enables users to discover the full range of Cloudinary’s capability and find the endpoints that fit their more complex and unique use cases.&lt;/p&gt;
&lt;h2&gt;The added benefits of OpenAPI and Scalar&lt;/h2&gt;
&lt;p&gt;Beyond covering many more use cases, the combination of OpenAPI and Scalar provides many benefits for Cloudinary and their users:&lt;/p&gt;
&lt;h3&gt;Functionality&lt;/h3&gt;
&lt;p&gt;An interactive API reference is basically an entire separate app. It requires:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;An OpenAPI parser to make the data usable&lt;/li&gt;
&lt;li&gt;A page generator for each of the endpoints&lt;/li&gt;
&lt;li&gt;Components with each page to handle URLs, authentication, and examples&lt;/li&gt;
&lt;li&gt;Templates for each example that need to work with specific client libraries&lt;/li&gt;
&lt;li&gt;And all this needs to load fast, look nice, and be responsive&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;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’t integrate with their existing website.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;As a bonus, Cloudinary customizes Scalar using CSS selectors to fit their style and brand. Users likely won’t notice it&apos;s something they haven’t built themselves.&lt;/p&gt;
&lt;h3&gt;Interactivity&lt;/h3&gt;
&lt;p&gt;If that functionality didn’t impress you, Scalar built an entire &lt;a href=&quot;http://client.scalar.com/&quot;&gt;API client&lt;/a&gt; into API references. This means users can send requests straight from Cloudinary’s docs, helping them get started without needing to build a sample app of their own.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Embracing the OpenAPI ecosystem&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;As an example, tools in this ecosystem can generate client SDKs (like Python or Ruby) from an OpenAPI document. These SDKs make setting up and using Cloudinary simpler and give another option for users with little added work for Cloudinary.&lt;/p&gt;
&lt;p&gt;OpenAPI also helps make developers at Cloudinary more productive by:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Generating code like the SDKs mentioned above&lt;/li&gt;
&lt;li&gt;Act as a source of truth for API functionality and purpose for internal and external stakeholders&lt;/li&gt;
&lt;li&gt;Ensure API development best practices like consistent endpoint design and descriptions&lt;/li&gt;&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;It’s one thing to say you want to make APIs accessible, but it’s a lot of work to actually do it.&lt;/p&gt;
&lt;p&gt;Cloudinary does a lot of this themselves, with their high quality docs and getting started guides, but also benefit from what’s being built in the OpenAPI ecosystem, like Scalar (and we’re happy to help 😎).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apr 28, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>An introduction to OpenAPI variables</title>
      <link>https://scalar.com/blog/posts/2025-04-23-an-introduction-to-openapi-variables</link>
      <guid>https://scalar.com/blog/posts/2025-04-23-an-introduction-to-openapi-variables</guid>
      <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;There are two types of OpenAPI variables:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Server variables:&lt;/strong&gt; Strings used specifically in the server URL, primarily used for configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameters:&lt;/strong&gt; Potentially complex data types used to define data passed through the API.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;This guide provides an overview of defining and using both.&lt;/p&gt;
&lt;h2&gt;Server variables&lt;/h2&gt;
&lt;p&gt;API requests need to go somewhere. That is what the URL defines. Server variables enable easy modification of the URL to support developer experience.&lt;/p&gt;
&lt;p&gt;For example, if you had an app running on a strange port of a customer’s subdomain, you could define a &lt;code&gt;customerId&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; variable like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;servers:
  - url: https://{customerId}.coolapp.com:{port}/v2
    variables:
      customerId:
        default: scalar
        description: Customer ID assigned by the service provider
      port:
        enum:
          - &apos;443&apos;
          - &apos;8443&apos;
        default: &apos;443&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps developers on your team know the constraints of the URL as well as easily test multiple configurations of it.&lt;/p&gt;
&lt;p&gt;Generally, server variables fit into one of the following use cases:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Configuring an API to work across dev, staging, and production environments&lt;/li&gt;
&lt;li&gt;Testing APIs across both &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; protocols&lt;/li&gt;
&lt;li&gt;Allowing flexibility in port selection&lt;/li&gt;
&lt;li&gt;Defining subdomain values for customers, regions, or multi-tenant applications.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Parameters&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;There are four main types of parameters:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Path&lt;/strong&gt;, used in URL paths like &lt;code&gt;/users/{id}&lt;/code&gt; to identify specific resources.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query&lt;/strong&gt;, added after a &lt;code&gt;?&lt;/code&gt; in the URL to optionally filter, sort, and paginate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header&lt;/strong&gt;, sent in HTTP for metadata, auth tokens, and tracking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cookie&lt;/strong&gt;, sent in the cookie header for things like session management, but generally avoided/not recommended.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;For example, a basic parameter to get a specific user might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/users/{id}:
  get:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
          minimum: 5
        description: The ID for the user&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each parameter, you need to define its &lt;code&gt;name&lt;/code&gt;, location (&lt;code&gt;in&lt;/code&gt;), and data type (using &lt;code&gt;schema&lt;/code&gt; or &lt;code&gt;content&lt;/code&gt;). Beyond these, there are several other optional attributes like &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;required&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Parameter attributes have many uses including:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints&lt;/strong&gt; including ones like maximum or minimum number values, string length and patterns, array uniqueness, and enum allowed values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt; including basic types like &lt;code&gt;integer&lt;/code&gt;, data formats like &lt;code&gt;int64&lt;/code&gt;, and complex objects (built of many constraints). This enables you to sanitize inputs and protect against attack vectors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Defaults&lt;/strong&gt; as well as defining whether null values are allowed with &lt;code&gt;nullable: true&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Style and serialization definition&lt;/strong&gt; AKA how arrays and objects are formatted in URLs and headers. For example, &lt;code&gt;ids=[1,2,3]&lt;/code&gt; changes to:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;?ids=1,2,3&lt;/code&gt; when &lt;code&gt;style: form&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?ids=1%202%203&lt;/code&gt; when &lt;code&gt;style: spaceDelimited&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?ids=1|2|3&lt;/code&gt; when &lt;code&gt;style: pipeDelimited&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content types&lt;/strong&gt; like &lt;code&gt;application/json&lt;/code&gt;, &lt;code&gt;application/xml&lt;/code&gt;, &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;, &lt;code&gt;text/plain&lt;/code&gt;, &lt;code&gt;image/png&lt;/code&gt;, &lt;code&gt;application/pdf&lt;/code&gt;, and their schemas.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;For example, to represent a filter that accepts a JSON object with a name and (optionally) age that looks like this &lt;code&gt;/api/endpoint?filter={&quot;name&quot;:&quot;John&quot;,&quot;age&quot;:25}&lt;/code&gt;, you can define the parameter like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Reusing parameters&lt;/h3&gt;
&lt;p&gt;As a bonus, parameters can also be reused so you don’t need to define them multiple times. This is done by defining them in the components section like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once done, you can reference them in your individual paths using &lt;code&gt;$ref&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;paths:
  /users:
    get:
      parameters:
        - $ref: &apos;#/components/parameters/ApiVersion&apos;
        - $ref: &apos;#/components/parameters/PaginationLimit&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In a way, this is a sort of meta-level variable, a variable inside a variable.&lt;/p&gt;
&lt;h2&gt;The limitation of OpenAPI variables&lt;/h2&gt;
&lt;p&gt;Although OpenAPI provides a lot of flexibility for defining and using variables, it’s not close to providing everything you could ever want:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Server variables can only be strings and have limited validation options.&lt;/li&gt;
&lt;li&gt;Parameters don’t support dynamic default values, computed values, or conditional values.&lt;/li&gt;
&lt;li&gt;Parameter schemas don’t support custom validation functions, complex data structures for queries, and other advanced features.&lt;/li&gt;
&lt;li&gt;Descriptions are limited to basic text limiting rich media, interactivity, and markdown.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Luckily, there’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.&lt;/p&gt;
&lt;p&gt;We know because this is exactly what we did when we added environment variables, code samples, internal controls, and more to &lt;a href=&quot;https://scalar.com/blog/posts/2025-04-06-how-we-extended-the-openapi-specification&quot;&gt;our version of the OpenAPI specification&lt;/a&gt;, which &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/openapi.md&quot;&gt;you can read here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apr 23, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How we extended the OpenAPI specification</title>
      <link>https://scalar.com/blog/posts/2025-04-06-how-we-extended-the-openapi-specification</link>
      <guid>https://scalar.com/blog/posts/2025-04-06-how-we-extended-the-openapi-specification</guid>
      <pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;We recently &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/documentation/openapi.md&quot;&gt;extended the OpenAPI specification&lt;/a&gt; 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.&lt;/p&gt;
&lt;h2&gt;What we added to the OpenAPI specification&lt;/h2&gt;
&lt;p&gt;At the moment, we have two main tools, our &lt;a href=&quot;https://docs.scalar.com/&quot;&gt;API reference&lt;/a&gt; and &lt;a href=&quot;https://client.scalar.com/&quot;&gt;client&lt;/a&gt;. Our extensions to the OpenAPI specification are to support the experience we want to provide with both of these tools. Specifically, they include:&lt;/p&gt;
&lt;h3&gt;1. Environments&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;You can set up an environment manually in the API client or you can set them up in your OpenAPI doc with &lt;code&gt;x-scalar-environments&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ... rest of your OpenAPI doc

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

  development:
    description: &apos;Development environment&apos;
    color: &apos;#7ED321&apos;
    variables:
      apiKey:
        description: &apos;Development API Key&apos;
        default: &apos;dev-key-456&apos;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Scalar environments&quot; title=&quot;Scalar environments&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On top of that, you can specify which environment should be the default with &lt;code&gt;x-scalar-active-environment&lt;/code&gt;. If you don’t set this, we just use the first &lt;code&gt;x-scalar-environments&lt;/code&gt; value.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ... rest of your OpenAPI doc

x-scalar-active-environment: development&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Code samples&lt;/h3&gt;
&lt;p&gt;Although we provide a bunch of code samples for everything from cURL to &lt;code&gt;http.client&lt;/code&gt; to &lt;code&gt;clj-http&lt;/code&gt;, there might be more code samples you want to add to your API reference. This is where &lt;code&gt;x-codeSamples&lt;/code&gt; comes in.&lt;/p&gt;
&lt;p&gt;Each of these contains a label, programming language, and source code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ... 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(&quot;YOUR_API_KEY&quot;)

            # Upload file with metadata
            response = client.upload_file(
                file_path=&quot;example.pdf&quot;,
                metadata={&quot;category&quot;: &quot;documents&quot;}
            )
            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:
        &apos;200&apos;:
          description: File uploaded successfully&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This then will show up as the sample for the operation in Scalar’s API reference.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;x-codeSamples&lt;/code&gt; are great for providing SDK examples, complex operations, language-specific features, and more.&lt;/p&gt;
&lt;h3&gt;3. Tags&lt;/h3&gt;
&lt;p&gt;Tags are used to group similar operations.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;To handle this, we added the &lt;code&gt;x-displayName&lt;/code&gt; 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 &lt;code&gt;pets_v1&lt;/code&gt; to &lt;code&gt;Pets&lt;/code&gt;, you can do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tags:
  - name: pets_v1
    x-displayName: Pets # This will display as &quot;Pets&quot; instead of &quot;pets_v1&quot;
    description: Pet management operations&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On top of this, larger OpenAPI docs often have a lot of unorganized tags and endpoints. We provide&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x-tagGroups:
  - name: Store Management
    tags:
      - stores_v1
      - inventory_v1
  - name: Pet Operations
    tags:
      - pets_v1
  - name: Human Resources
    tags:
      - employees_v1&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Internal&lt;/h3&gt;
&lt;p&gt;Because your OpenAPI doc might include endpoints you don’t want published, we can help hide them from the API reference and client using&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/system/cache/clear:
  post:
    summary: Clear system cache
    description: Internal endpoint to clear the application cache
    x-internal: true
    responses:
      &apos;200&apos;:
        description: Cache cleared successfully&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Additional properties&lt;/h3&gt;
&lt;p&gt;Finally, a little quality of life improvement for additional properties. Although normal properties have known fixed names, additional properties do not. If you’d like to add a name, you can use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MetadataObject:
  type: object
  properties:
    createdAt:
      type: string
      format: date-time
  additionalProperties:
    x-additionalPropertiesName: metadataField
    type: object
    description: Dynamic metadata fields&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How we handle these under the hood&lt;/h2&gt;
&lt;p&gt;The work to handle this extended OpenAPI spec and make it actually do something in Scalar’s API client and reference requires schema validation, integration with our store, and UI implementation.&lt;/p&gt;
&lt;h3&gt;1. Schema validation&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const xScalarEnvironmentSchema = z.object({
  description: z.string().optional(),
  color: z.string().optional(),
  variables: z.record(z.string(), xScalarEnvVarSchema),
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we change the schema, we don’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 &lt;code&gt;x-scalar-environments&lt;/code&gt; looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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[&apos;collections&apos;]
  &gt;((prev, c) =&gt; {
    prev[c.uid] = {
      ...c,
      &apos;x-scalar-environments&apos;: c[&apos;x-scalar-environments&apos;] || {},
    }
    return prev
  }, {})
  // ... rest of your code&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. State management&lt;/h3&gt;
&lt;p&gt;The schema then integrates with the relevant store and its associated mutators. For example, &lt;code&gt;x-scalar-environments&lt;/code&gt; integrates with the store responsible for environment handling like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 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: &apos;&apos;,
        name: &apos;No Environment&apos;,
        value: JSON.stringify(activeWorkspace.value.environments, null, 2),
      }
    }

    // Find environment in collections
    const activeEnvironmentCollection = activeWorkspaceCollections.value.find(
      (c) =&gt; c[&apos;x-scalar-environments&apos;]?.[activeWorkspace.value.activeEnvironmentId]
    )
    // ...&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. UI implementation&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;On top of this, the complexity varies dramatically:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;x-displayName&lt;/code&gt;, &lt;code&gt;x-tagGroup&lt;/code&gt;, &lt;code&gt;x-internal&lt;/code&gt;, &lt;code&gt;x-additionalPropertiesName&lt;/code&gt; are all relatively straightforward conditional rendering.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-codeSamples&lt;/code&gt; integrates with the existing code sample code.&lt;/li&gt;
&lt;li&gt;The most complicated is &lt;code&gt;x-scalar-environments&lt;/code&gt; 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.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;In the end, most of what you see is small improvements to the overall experience of using OpenAPI docs with Scalar’s API client and reference. &lt;strong&gt;This is the point.&lt;/strong&gt; We know these small improvements add up to create the best possible experience for API developers.&lt;/p&gt;
&lt;h2&gt;Why extend the OpenAPI specification?&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apr 6, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>A guide to OpenAPI security (and how we handle it in Scalar)</title>
      <link>https://scalar.com/blog/posts/2025-03-26-a-guide-to-openapi-security-and-how</link>
      <guid>https://scalar.com/blog/posts/2025-03-26-a-guide-to-openapi-security-and-how</guid>
      <pubDate>Wed, 26 Mar 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;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’s often not obvious how to interact with these security mechanisms, especially when it comes to APIs.&lt;/p&gt;
&lt;p&gt;This is where defining security in OpenAPI documentation comes in. This enables you to define the authentication and authorization mechanisms you’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.&lt;/p&gt;
&lt;p&gt;This post details the types of security, how to define them in your OpenAPI doc, and how they work in Scalar’s API reference and client.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What about security definitions?&lt;/strong&gt; This was the name of the same concept security schemes in OpenAPI 2 (formerly Swagger).&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2&gt;The many types of security for OpenAPI docs&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;API keys:&lt;/strong&gt; These can be sent in headers, query parameters, or cookies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP:&lt;/strong&gt; This includes both basic username and password as well as bearer tokens. You can also define the &lt;code&gt;bearerFormat&lt;/code&gt; such as &lt;code&gt;JWT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OAuth 2.0:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenID Connect:&lt;/strong&gt; Finally, OpenID builds on top of OAuth 2.0. It allows clients to automatically get configuration information using a “well-known” discovery endpoint.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Each of these can be combined using &lt;code&gt;AND/OR&lt;/code&gt; logic. They can also be applied globally or per-operation.&lt;/p&gt;
&lt;h2&gt;How to define OpenAPI security&lt;/h2&gt;
&lt;p&gt;Defining your OpenAPI security starts by adding the &lt;code&gt;securitySchemes&lt;/code&gt; property. This defines all the security schemes your API supports. You then use &lt;code&gt;security&lt;/code&gt; to apply the schemes globally or per-operation.&lt;/p&gt;
&lt;p&gt;You can think of &lt;code&gt;securitySchemes&lt;/code&gt; like the ID types available (passport, driver’s license, badge) while &lt;code&gt;security&lt;/code&gt; specifies which IDs are required to enter.&lt;/p&gt;
&lt;p&gt;For example, if you want to include either &lt;code&gt;BasicAuth&lt;/code&gt; or &lt;code&gt;ApiKeyAuth&lt;/code&gt; anywhere in your OpenAPI doc, it starts by defining them as &lt;code&gt;securitySchemes&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;securitySchemes&lt;/code&gt; defined, you can then apply them at the global level by adding &lt;code&gt;security&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;security:
  - ApiKeyAuth: []
  - BasicAuth: []

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

components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
      description: Basic Authentication using username and password
# ... rest of your OpenAPI file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply it to a single operation, define the &lt;code&gt;security&lt;/code&gt; property on the path like we do here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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:
        &apos;201&apos;:
          description: Order created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  orderId:
                    type: integer
                  status:
                    type: string
        &apos;401&apos;:
          description: Unauthorized - invalid or missing API key
  # ... rest of your OpenAPI file&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each security scheme also has different properties beyond &lt;code&gt;type&lt;/code&gt; and the optional &lt;code&gt;description&lt;/code&gt; you can define. For example:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;apiKey&lt;/code&gt; has &lt;code&gt;in&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http&lt;/code&gt; has &lt;code&gt;scheme&lt;/code&gt; and &lt;code&gt;bearerFormat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth2&lt;/code&gt; has &lt;code&gt;flows&lt;/code&gt; which has &lt;code&gt;authorizationCode&lt;/code&gt;, &lt;code&gt;implicit&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt; each with their own properties&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OpenIdConnect&lt;/code&gt; has &lt;code&gt;openIdConnectUrl&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;Defining security with your framework&lt;/h3&gt;
&lt;p&gt;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’s an example for &lt;a href=&quot;https://github.com/honojs/middleware/tree/main/packages/zod-openapi#how-to-setup-authorization&quot;&gt;Hono&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Register the security scheme in your OpenAPI docs
app.openAPIRegistry.registerComponent(&apos;securitySchemes&apos;, &apos;Bearer&apos;, {
  type: &apos;http&apos;,
  scheme: &apos;bearer&apos;,
  description: &apos;Enter your JWT token in the format: Bearer &amp;#x3C;token&gt;&apos;,
})

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

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

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

app = FastAPI(
    title=&quot;Protected API&quot;,
    description=&quot;API secured with HTTP Basic Auth&quot;
)

security = HTTPBasic()

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

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

@app.get(&quot;/secure-data/&quot;,
    summary=&quot;Get protected data&quot;,
    responses={
        401: {&quot;description&quot;: &quot;Invalid credentials&quot;},
        200: {&quot;description&quot;: &quot;Successfully authenticated&quot;}
    })
def secure_data(username: str = Depends(verify_credentials)):
    return {&quot;message&quot;: f&quot;Hello, {username}&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is where it really pays off to have a framework that generates the OpenAPI doc (well) for you. For example, Django doesn’t generate an OpenAPI doc automatically, making interacting with it for API reference or debugging a lot harder.&lt;/p&gt;
&lt;h2&gt;How Scalar handles security&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;API reference&lt;/h3&gt;
&lt;p&gt;Our &lt;a href=&quot;https://galaxy.scalar.com/&quot;&gt;API reference&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;API client&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://client.scalar.com/&quot;&gt;API client&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;To handle this, we store auth values at the collection-level, support operation-level overrides, and allow you to select the scheme you want on per-request. The selectors for schemes are also more interactive, allowing editing of API key name fields and scheme deletion.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Supporting API security and developer experience&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Security shouldn’t come at the cost of developer experience. The tools Scalar provides help developers authenticate quickly and correctly. This helps them test endpoints, debug, and ultimately build better (and more secure) APIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 26, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>The hidden complexity of building drag and drop</title>
      <link>https://scalar.com/blog/posts/2025-03-19-the-hidden-complexity-of-building</link>
      <guid>https://scalar.com/blog/posts/2025-03-19-the-hidden-complexity-of-building</guid>
      <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Users expect that same pattern to work in the apps they use. Unfortunately for those who have to implement this, gravity isn’t built into computers. Writing all the logic needed to drag and drop in an app is a lot of work.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;What can you drag and drop in Scalar’s API client?&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;On top of this, drag and drop can be used to import OpenAPI docs. This can be done both by dragging in &lt;code&gt;.yml&lt;/code&gt;, &lt;code&gt;.yaml&lt;/code&gt;, and &lt;code&gt;.json&lt;/code&gt; files as well as URLs.&lt;/p&gt;
&lt;h2&gt;What’s so tricky about drag and drop?&lt;/h2&gt;
&lt;p&gt;Creating a slick drag and drop experience required us to solve the following challenges:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Where.&lt;/strong&gt; You can’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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State management&lt;/strong&gt;. 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 need to be handled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance.&lt;/strong&gt; When you drag an element, your browser fires &lt;code&gt;dragover&lt;/code&gt; events many times per second. If you don’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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context.&lt;/strong&gt; Requests and collections aren’t simple. They include information on authentication settings, examples, test cases, response history, security schemes, and servers. Behind this is a structured OpenAPI document that needs to stay valid.&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;How does our API client handle drag and drop challenges?&lt;/h2&gt;
&lt;p&gt;Rather than existing libraries like &lt;code&gt;react-dnd&lt;/code&gt;, &lt;code&gt;vue-draggable&lt;/code&gt;, we developed our own &lt;a href=&quot;https://github.com/scalar/scalar/tree/main/packages/draggable&quot;&gt;@scalar/draggable package&lt;/a&gt; with a &lt;a href=&quot;https://github.com/scalar/scalar/blob/main/packages/draggable/src/Draggable.vue&quot;&gt;Draggable.vue component&lt;/a&gt;. To make dragging and dropping consistent across the API client, the component uses standard HTML 5 drag and drop APIs to:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Track dragging and hovering state&lt;/li&gt;
&lt;li&gt;Handle drag events&lt;/li&gt;
&lt;li&gt;Provide visual feedback&lt;/li&gt;
&lt;li&gt;Customize drop zones&lt;/li&gt;
&lt;li&gt;Track hierarchy for validation&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Here’s how we specifically handle some of the challenges with dragging and dropping we identified:&lt;/p&gt;
&lt;h3&gt;Validation&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Draggable.vue&lt;/code&gt; component enables us to define droppable checks unique to each component and object type. For example, requests and folders can’t be dropped in lower-level containers, they can’t receive drops, and they can’t be read only:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 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&apos;t be dropped anywhere
  return true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Collections can be reordered but not dropped in “Drafts”, can receive requests and folders as dropped but only as children, and can’t be read only:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Request.vue (for collections)
const _isDroppable = (draggingItem, hoveredItem) =&gt; {
  if (activeWorkspace.value.isReadOnly) return false
  if (!collections[draggingItem.id] &amp;#x26;&amp;#x26; hoveredItem.offset !== 2) return false // Requests/folders must be dropped AS CHILDREN
  if (
    collections[draggingItem.id] &amp;#x26;&amp;#x26;
    collections[hoveredItem.id]?.spec?.info?.title === &apos;Drafts&apos;
  )
    return false
  return true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Draggable.vue&lt;/code&gt; component also has hierarchy validation to ensure an object isn’t highlighted if hovering over itself or child. It also has configurable position-based validation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const getDraggableOffsets = computed(() =&gt; {
  let ceiling = 0.5
  let floor = 0.5

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

  return { ceiling, floor }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All of this is implemented through props passed down to the draggable component. This ensures that the &lt;code&gt;Draggable&lt;/code&gt; component is scoped to only the drag and drop logic it needs to handle and makes it more reusable.&lt;/p&gt;
&lt;h3&gt;Visual feedback&lt;/h3&gt;
&lt;p&gt;We provide feedback at 3 spots for dragging and dropping:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Above.&lt;/strong&gt; Uses the &lt;code&gt;dragover-above&lt;/code&gt; class to show feedback when hovering above an item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Below.&lt;/strong&gt; Uses the &lt;code&gt;dragover-below&lt;/code&gt; class to show feedback when hovering below an item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;As a child.&lt;/strong&gt; Uses the &lt;code&gt;dragover-asChild&lt;/code&gt; class when hovering to nest inside an item.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Beyond this, there are a few styling things we do to make this look good. The styles are implemented using CSS pseudo-elements (&lt;code&gt;:after&lt;/code&gt;), we use &lt;code&gt;pointer-events: none&lt;/code&gt; to ensure indicators don’t interfere with drag operations, and use CSS &lt;code&gt;color-mix()&lt;/code&gt; to create a subtle, semi-transparent effect that matches the UI theme.&lt;/p&gt;
&lt;h3&gt;Performance&lt;/h3&gt;
&lt;p&gt;Because the &lt;code&gt;dragover&lt;/code&gt; 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:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Limits the &lt;code&gt;dragover&lt;/code&gt; handler to firing at most once every 25 milliseconds (40 times per second)&lt;/li&gt;
&lt;li&gt;Drops intermediate events during that 25ms window&lt;/li&gt;
&lt;li&gt;Still ensures enough updates to maintain smooth visual feedback&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;This function looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const throttle = &amp;#x3C;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
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It takes a function and a time limit, returns a wrapped version that only executes once within the time window, and tracks whether we’re still in the throttle.&lt;/p&gt;
&lt;p&gt;This is enough to provide smooth (40 fps) animations while still significantly reducing the processing overhead of raw &lt;code&gt;dragover&lt;/code&gt; events.&lt;/p&gt;
&lt;h3&gt;State management&lt;/h3&gt;
&lt;p&gt;The core &lt;code&gt;Draggable.vue&lt;/code&gt; component doesn’t handle state, only hover positions as well as drag start and end events with that position information.&lt;/p&gt;
&lt;p&gt;The actual state management is lifted to the parent component &lt;code&gt;Request.vue&lt;/code&gt;. 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.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const mutate = (uid: string, childUids: string[]) =&gt; {
  if (collections[uid]) collectionMutators.edit(uid, &apos;childUids&apos;, childUids)
  else if (folders[uid]) folderMutators.edit(uid, &apos;childUids&apos;, childUids)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;The undersurface complexity of drag and drop&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;This is representative of one of many of the little optimizations we do to try to make working with APIs as easy as possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 19, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How we sped up our API docs 25x</title>
      <link>https://scalar.com/blog/posts/2025-03-12-how-we-sped-up-our-api-docs-25x</link>
      <guid>https://scalar.com/blog/posts/2025-03-12-how-we-sped-up-our-api-docs-25x</guid>
      <pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Diagnosing our performance issues&lt;/h2&gt;
&lt;p&gt;The obvious first spot to look at is how data is imported into the client.&lt;/p&gt;
&lt;p&gt;To check this, Amrit sprinkled &lt;code&gt;console.time&lt;/code&gt; and &lt;code&gt;console.timeEnd&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://github.com/scalar/scalar/pull/3175/files#diff-6221b31824b2fb1816c1780e614fe3dc504ab9cc7ff6b8fcc305ac813cee8bd3&quot;&gt;Request Sidebar&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By timing the &lt;code&gt;onBeforeMount&lt;/code&gt; and &lt;code&gt;onMounted&lt;/code&gt; hooks, he found that a 7 MB OpenAPI spec took 2.6 seconds to mount (way too long). Narrowing down, it was specifically the &lt;a href=&quot;https://github.com/scalar/scalar/pull/3175/files#diff-3179b1678c7f778417b849d87878807ad518233ca909f20fa108f7fe4e8784ef&quot;&gt;Request Sidebar Item&lt;/a&gt; that was causing the issue.&lt;/p&gt;
&lt;p&gt;Each instance of &lt;code&gt;RequestSidebarItem&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2&gt;Refactoring our sidebar to fix the performance issues&lt;/h2&gt;
&lt;p&gt;Fixing this required us to rework how users edit sidebar items.&lt;/p&gt;
&lt;p&gt;To start, Amrit hoisted the edit and rename modals outside of &lt;code&gt;RequestSidebarItem&lt;/code&gt; 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&apos;t add functionality beyond what was already there, so he removed it.&lt;/p&gt;
&lt;p&gt;The trickiest fix was the dropdown menu. &lt;code&gt;headlessui&lt;/code&gt;, a component library we use, makes it harder to open the menu when the triggering button wasn’t a child of the component. This is why the menu was repeated inside every instance of &lt;code&gt;RequestSidebarItem&lt;/code&gt;; we needed the &lt;code&gt;...&lt;/code&gt; button to open it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Opening this menu in the right spot required:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Passing a &lt;code&gt;targetRef&lt;/code&gt; to both the floating popup and dropdown menu.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Manually focusing on the dropdown menu once opened and setting up a global click listener to close it.&lt;/li&gt;&lt;/ol&gt;
&lt;h2&gt;Maintaining functionality while improving performance&lt;/h2&gt;
&lt;p&gt;Most users probably didn&apos;t notice changes to the sidebar functionality. It looks the same and works the same.&lt;/p&gt;
&lt;p&gt;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 improved 25x, going from 2.6s to 0.11s.&lt;/p&gt;
&lt;p&gt;Improving performance is a continual process. For example, immediately after this, we upgraded to Vue 3.5, which came with &lt;a href=&quot;https://blog.vuejs.org/posts/vue-3-5#reactivity-system-optimizations&quot;&gt;reactivity system optimizations&lt;/a&gt;. As a company building for developers, we know how important performance is, so expect these improvements to continue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 12, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How .NET 9 and Scalar solve the problem of under-documented APIs</title>
      <link>https://scalar.com/blog/posts/2025-03-05-how-net-9-and-scalar-solve-the-problem</link>
      <guid>https://scalar.com/blog/posts/2025-03-05-how-net-9-and-scalar-solve-the-problem</guid>
      <pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate>
      <description>&lt;p&gt;Stop me if you’ve heard this one before:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;You finish and ship a change to your API.&lt;/li&gt;
&lt;li&gt;You don’t bother to document it.&lt;/li&gt;
&lt;li&gt;You forget to generate and/or update the API docs.&lt;/li&gt;
&lt;li&gt;You definitely don’t publish the newly updated API docs so others can actually use it.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;We don’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.&lt;/p&gt;
&lt;p&gt;With the release of .NET 9 in combination with Scalar, we are changing this.&lt;/p&gt;
&lt;h2&gt;What’s changed with the .NET 9 release?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ASP.NET&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;To fix this gap, the ASP.NET Core team is &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/54599&quot;&gt;removing Swashbuckle&lt;/a&gt; with the release of .NET 9 and replacing it with their own solution for OpenAPI document generation.&lt;/p&gt;
&lt;p&gt;What this looks like is built-in support for OpenAPI document generation via the &lt;code&gt;Microsoft.AspNetCore.OpenApi&lt;/code&gt; and &lt;code&gt;Microsoft.Extensions.ApiDescription.Server&lt;/code&gt; packages. This &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&amp;#x26;tabs=visual-studio%2Cminimal-apis#using-scalar-for-interactive-api-documentation&quot;&gt;generates&lt;/a&gt; a JSON OpenAPI document automatically based on a map of the API.&lt;/p&gt;
&lt;p&gt;For example, after installing both packages, add &lt;code&gt;builder.Services.AddOpenApi();&lt;/code&gt; and &lt;code&gt;app.MapOpenApi();&lt;/code&gt; to your &lt;code&gt;Program.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

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

app.Run();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this then generates an OpenAPI document at &lt;code&gt;https://localhost:&amp;#x3C;port&gt;/openapi/v1.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Dotnet 9 OpenAPI&quot; title=&quot;Dotnet 9 OpenAPI&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Generating this 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’s suite of API tools come in.&lt;/p&gt;
&lt;h2&gt;Setting up Scalar’s ASP.NET package&lt;/h2&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://github.com/captainsafia&quot;&gt;@captainsafia&lt;/a&gt; and &lt;a href=&quot;https://github.com/xC0dex&quot;&gt;@xC0dex&lt;/a&gt; of our community, Scalar has an ASP.NET API package.&lt;/p&gt;
&lt;p&gt;To set it up, simply install the package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package Scalar.AspNetCore&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add the directive and &lt;code&gt;MapScalarApiReference()&lt;/code&gt; to your program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

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

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

app.Run();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like magic, this gives you a full set of API docs when you navigate to &lt;code&gt;https://localhost:&amp;#x3C;port&gt;/scalar/v1&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Dotnet 9 API docs&quot; title=&quot;Dotnet 9 API docs&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Under the hood of how Scalar generates your API docs&lt;/h2&gt;
&lt;p&gt;You might be asking “how did that happen so quickly?” Well, when you add and run Scalar in your ASP.NET API, we do the following:&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Configure Scalar options including the location of your OpenAPI document and any customizations you want like titles, themes, and authentication&lt;/li&gt;
&lt;li&gt;Map a GET endpoint to your API that serves a page.&lt;/li&gt;
&lt;li&gt;On that page, use your OpenAPI document and Scalar configuration to load your version of Scalar’s API docs from a CDN.&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;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, &lt;a href=&quot;https://docs.scalar.com/register&quot;&gt;you can create an account&lt;/a&gt; and edit your docs however you like.&lt;/p&gt;
&lt;h2&gt;Solving the under-documented API problem&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;We don’t stop there either. Our API seamlessly turns 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.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;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&quot;&gt;&lt;img src=&quot;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&quot; alt=&quot;Dotnet 9 API client&quot; title=&quot;Dotnet 9 API client&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mar 5, 2025&lt;/strong&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Scalar Joins the Open-Source Pledge</title>
      <link>https://scalar.com/blog/posts/2024-09-01-oss-pledge</link>
      <guid>https://scalar.com/blog/posts/2024-09-01-oss-pledge</guid>
      <pubDate>Sun, 01 Sep 2024 00:00:00 GMT</pubDate>
      <description>&lt;h2&gt;Sep 1st, 2024&lt;/h2&gt;
&lt;p&gt;What is OSS Pledge?&lt;/p&gt;
&lt;p&gt;You can read more about it &lt;a href=&quot;https://osspledge.com/&quot;&gt;here&lt;/a&gt;, but in short, it&apos;s paying Open Source maintainers. The minimum to participate is $2,000 per full-time employed developer per year.&lt;/p&gt;
&lt;p&gt;When we met Chad Whitacre at the Open-Source Summit in Seattle earlier this year, we instantly bonded over all things open-source! He also told me about the very early stages of the Open-Source Pledge that Sentry was starting and I couldn&apos;t have been more excited about joining it.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://scalar.com/blog/oss-pledge.jpg&quot; alt=&quot;&quot;&gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://x.com/getsentry&quot;&gt;@getsentry&lt;/a&gt; &amp;#x26; &lt;a href=&quot;https://x.com/scalar&quot;&gt;@scalar&lt;/a&gt; love open-source!&lt;/p&gt;
&lt;p&gt;w &lt;a href=&quot;https://x.com/__chadwhitacre&quot;&gt;@__chadwhitacre&lt;/a&gt; &lt;a href=&quot;https://x.com/linuxfoundation&quot;&gt;@linuxfoundation&lt;/a&gt; #OSSummit&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/MarcLaventure/status/1782403644387922189/&quot;&gt;@marclaventure on X&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;How much Scalar Gives&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;litestar-org $250/month &lt;a href=&quot;https://litestar.dev/&quot;&gt;(LiteStar)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SaltyAom $300/month &lt;a href=&quot;https://elysiajs.com/&quot;&gt;(ElysiaJS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;colinhacks $99/month &lt;a href=&quot;https://zod.dev/&quot;&gt;(Zod)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;tiangolo $500/month &lt;a href=&quot;https://fastapi.tiangolo.com/&quot;&gt;(FastAPI)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;yusukebe $100/month &lt;a href=&quot;https://hono.dev/&quot;&gt;(Hono)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;marijnh $5/month &lt;a href=&quot;https://github.com/marijnh&quot;&gt;(CodeMirror/ProseMirror)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;daveshanley $100/month &lt;a href=&quot;https://github.com/pb33f/libopenapi&quot;&gt;(libopenapi)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LinusBorg $5/month &lt;a href=&quot;https://github.com/LinusBorg&quot;&gt;(Vue.js Maintainer)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;pi0 $31/month &lt;a href=&quot;https://github.com/pi0&quot;&gt;(Nuxt/UnJS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;dmonad $10/month &lt;a href=&quot;https://github.com/dmonad&quot;&gt;(YJS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;thibaultleouay $10/month &lt;a href=&quot;https://www.openstatus.dev/&quot;&gt;(OpenStatus)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;fuma-nama $10/month &lt;a href=&quot;https://github.com/fuma-nama&quot;&gt;(FumaDocs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;litestar + python (3,529.34)&lt;/li&gt;
&lt;li&gt;algoria OSS Bounty TypeScript Challenge + Daniel Roe (5,000)&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Thank You OSS&lt;/p&gt;
&lt;p&gt;Most of the team at Scalar have started their Engineering careers because of Open-Source, and we couldn&apos;t be more honoured to be a part of the OSS Pledge! Thanks to all the maintainers and contributors whose software we are privileged to use and build with.&lt;/p&gt;
&lt;p&gt;Marc, CEO &amp;#x26; co-founder, Scalar&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
