All posts
This is some text inside of a div block.
General

How to write a great OpenAPI spec

An OpenAPI specification is the most popular standard for describing HTTP APIs. Originally known as a Swagger specification, OpenAPI enables API developers to define structure, methods, inputs, return types, webhooks, and more.

OpenAPI is a loose standard. It features a few required properties, but developers have to choose the level of detail to describe their API. As an SDK generation company, we’re generally agnostic about a specification’s structure from a customer standpoint. By design, we work with anything. However, having advised countless companies on their specifications, we’ve formed opinions on what makes for a great specification.

Why use OpenAPI?

Technically speaking, APIs do not need a specification. A specification is reactive; it describes the API. It’s a map, not a blueprint. The API, itself, is the source of truth. And specification describes everything about it. This includes the APIs routes. The inputs. The expected outputs. Etc.

API specifications and API design are two distinct topics. However, enforcing certain practices for an API specification does translate into API design recommendations.

With additional tooling, specifications are used to generate two important assets to make the APIs more accessible to users: (a) documentation and (b) SDKs. Both docs and SDKs can be handwritten, but a manual approach often leads to inconsistencies between API and API specifications.

OpenAPI is popular and well-supported, in the same regard that package.json is the common Javascript package manager, and Git is the common version tracking software. OpenAPI is well-established, is an open standard, minimizes vendor lock-in given its open-source status, and boasts strong community-built tool chains (e.g. linters).

How do you start an OpenAPI specification?

A basic choice for creating an OpenAPI specification is to handwrite it, following the standard. Nowadays, some developers may opt to use ChatGPT or another LLM for generating an initial OpenAPI spec based on their code base or API docs.

For organizations that are looking for a more full-stack approach, an API framework is preferable. API frameworks like FastAPI have an OpenAPI auto-generation feature, where the specification is automatically created from the API’s structure. The benefit of this closely-knit approach is that the spec naturally stays in sync with the implemented API. FastAPI notably allows developers to make modifications to the generated specification.

Alternatively, some developers choose to use linters to ensure a specification is correct (according to a standard guide). But standards are flexible, and linting can be overly strict, where errors may not translate to actionable tasks.

Follow common patterns when writing your OpenAPI specification

Our core advice when writing a strong OpenAPI specification is to follow community-recognized patterns.

OpenAPI is an intentionally un-opinionated standard. For example, a developer could use Spongebob-case when naming a route (e.g. POST /cReATeREcoRd), but that obviously wouldn’t be well received. This applies for the small stuff (e.g. casing, descriptions) to larger things (e.g. pagination, webhooks, RESTful concepts).

For each of these categories, there typically isn’t just one valid approach, but a few recognized patterns. What matters is choosing one and committing to it throughout the API.

Pagination

Pagination is where an API route can incrementally return entries from a lengthy list to minimize latency per request. For pagination, it is ideal for developers to use one of the three common techniques.

The first is offset pagination, where developers declare a limit and an offset, incrementing the offset to return the next set of entries. The next is page-based pagination, where entries are already bucketed into indexed pages, and developers just need to declare the page indices that they’re querying. The final is cursor pagination (or keyset or seek pagination), where developers provide an identifier to a specific entity and a limit, seeking a subset of the entities that follow it.

Because all of these techniques are popular, we decided to support all with Stainless.

Webhooks

Webhooks enable APIs to push data to customers whenever a specified event happens. They are the push alternative to a classical API request’s pull.

There is some out-of-date documentation online on how to support webhooks with OpenAPI because the standard didn’t exactly support them until version 3.1. Today, developers should use OpenAPI’s webhooks field, where it can be named and have a request payload defined.

When designing a webhook, developers should consider Standard Webhooks, an open-source initiative attempting to standardize webhook practices (Stainless is a supporter of their work).

Bias towards simplicity

OpenAPI specs can describe complex types, some of which that are hard to express in some languages.

Unions (described in the OpenAPI spec by anyOf and oneOf) are good examples of this. In Go, unions present a usability challenge compared to languages like Node and Python, since there’s no language-native way to correctly represent a union. We generate them as interfaces, but that presents challenges to the end-user, such as more complicated types, a higher learning curve, and complications around having primitive types like string or int64  as a union member.

oneOf also has surprising behavior. In the OpenAPI spec, oneOf  means that a value must match exactly one of the schemas provided.


oneOf:
	- type: string
	- enum: ['account']


While you may expect this to mean “any string, and you can pass account”, it actually means “any string except account", since account would match both schemas.

We interpret both oneOf and anyOf as a TypeScript union, which we find matches user expectations, but even still unions tend to impose mental overhead for end users of the SDK.

In general, we advise our customers to bias towards simplicity, rather than using the full expressive power of the OpenAPI spec. There are places where these more complex OpenAPI spec constructs are useful and desired, but you should be mindful that it can create more code downstream for your end users.

Treat your API specification as documentation

Because OpenAPI specifications can be used to also generate documentation, it is recommended to concisely document the API. This prevents documentation from going out-of-sync from the API specification (and, with the right processes, the underlying API).

Additionally, because OpenAPI specifications can be used to generate SDKs via tools like Stainless, type-ahead hints are auto-populated by embedded documentation in the spec. And, by using the spec as a source of truth, these type-ahead hints will remain in-sync with the main documentation.

Notably, OpenAPI supports Markdown for all of its description fields, enabling developers to create content-rich descriptions throughout the API. These descriptions should ideally follow similar grammatical conventions (voice, tense), be of predictable lengths, and concisely describe every API route. A common style guide that we support is Redocly’s guide for OpenAPI Markdown.

OpenAPI specifications can occasionally be a challenge for distributed teams

OpenAPI specifications can be a particular challenge for organizations where API ownership is federated internally. Some companies have multiple internal teams that are independently generating various specifications that need to be merged together. This engineering layout dramatically increases the odds of an inconsistency.

To address this, teams should use strict linting tools and ensure that any developer on an API team is cognizant of the API’s style guide.

Like any development project, OpenAPI specifications should live in a version-controlled repository (e.g. GitHub). For smaller teams, they’ll typically be in the same repository as the code that powers the API; this helps aid synchronized changes to both API and the API specification. For larger teams, an independent repository is more common, especially if various teams are contributing to the specification.

For API specifications that are used to generate SDKs, breaking changes in the spec don’t only amount to inaccurate documentation, but also broken logic. Accordingly, API developers need to be attuned to consistency. Additionally, some tools, like Optic, can help detect breaking changes.

How do you leverage OpenAPI specification for more?

Stainless was designed with imperfect OpenAPI specs in mind. You can use our SDK Studio to reorganize resources, change naming conventions, and apply transformations, without having to modify your OpenAPI spec to get started with publishing an SDK. We will generate diagnostics for your spec to help with polishing. For example, our diagnostics aid resolving type ambiguity, extracting shared models, and creating examples. You can get started with Stainless for free, and if you don’t have an OpenAPI specification yet, we will get you set up with our PetStore example spec.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Posted by
Stainless Team
Stainless Team
Generate best-in-class SDKs.