10 essentials when creating an Open API specification

Creating an OpenAPI provides you with the consistency and repeatability needed to easily provide a definition of your API to others. Below, we’ll run through what the OpenAPI Specification is, including OpenAPI vs Swagger, then look at 10 essentials for creating OpenAPIs.

What is OpenAPI?

The Open API Specification is a specification language for HTTP APIs. It brings consistency and standardisation to them by defining the structure and syntax, irrespective of the programming language.

What is an Open API good for? Well, the key benefit of using OpenAPIs is that they are easy for others to consume thanks to their standardisation and the fact that they are not coupled with any particular programming language.

OpenAPI Specification

The Open API definition and concept resulted from work begun by Tony Tam, who began developing Swagger in 2010. This was eventually rolled into the OpenAPI Initiative, sponsored by the Linux Foundation and with founders including IBM, Google, Microsoft, PayPal, Capital One and others. The Open API vs Swagger name discussions were put to bed on 1 January 2016, when the specification officially became the OpenAPI Specification (OAS). OpenAPI 3.0 was released in July 2017, with version 3.1.0 released in February 2021.

OpenAPI definitions, Open API versioning and more are all covered by the OpenAPI 3 specification, which can be adopted for all HTTP open source APIs.

Best essentials when creating an OpenAPI Specification

If you’ve embraced the Open API meaning and methodology and are now keen to create the best possible OpenAPI specification example, read on. We have developed a set of essentials for producing better API descriptions using the OpenAPI Specification. This article summarizes these essentials, which you can use to improve the description for your API design. This list isn’t comprehensive and contains some subjective recommendations, so adjust them as needed.

1. Compose a clear and concise API title

The title is one of the most important aspects of any OAS description. Too often, I see API titles such as:

  • some-service-name
  • auto-generated-api-name
  • Account service
  • Bob’s API

Your API title is an opportunity to convey a lot of information in a concise manner. It helps set the context for what to expect within the API. Consider the following title examples:

  • Bookstore Inventory
  • Account Profile Management
  • Image Conversion
  • Inventory Threshold Management

Which ones are easier to understand? Which might stand out if you were looking for a specific API?

2. Write a comprehensive API description

Once you establish the context of the API through the title, the description offers the next layer of understanding. It helps the reader understand what the API does, without needing to scroll through operation after operation within the file. Between the API title and description, a developer will make a choice whether to continue to pursue understanding the API in-depth or walk away.

Bad examples:

  • <empty>
  • <the API title>
  • Manages account profiles
  • Account service
  • An eCommerce API

Below is a better example:

 description: |
    The Bookstore eCommerce API supports the shopping experience of an online bookstore. The API includes the following capabilities and operations:

    __List Recent Books:__

    * List recently added books
    * Filtered list of books
    * View book details
    
    __Place an Order:__

    * Create cart
    * Add book to a cart
    * Remove book from a cart
    * Modify book already in a cart
    * View cart (including total price)
    * Create an order from cart

    The following resource collections are offered by this API:

      * Books - represents the inventory available by the bookstore
      * Carts - supports shopping for books until ready for converting to an order
      * Cart Items - tracks the book + quantity added to a cart
      * Orders - a cart that has been converted to an order that may be tracked to delivery
      * Order Payments - tracks credit card payments applied to an order

We start with the broad overview, then add more detail. The description starts by providing some basic context about what the Open API does, then adding capabilities offered by the outline, then operations. Anyone viewing this API description will quickly determine if this is the API they need, without having to continuously scroll and read each operation individually. For organisations with hundreds to thousands of APIs, this style of description is essential to finding the API you need fast.

3. Take advantage of clear and useful operation identifiers

Operation identifiers, found in OAS as operationId, are an optional value that may be used to reference an operation from tooling or (with OAS 3.0) from a Link object. The OAS 3.0 spec refers to the operationId with the following:

“Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.”

While not required, the operationId is encouraged. But little direction is given regarding naming conventions or how to create an operationId other than “to follow common programming naming conventions”.

I’ve seen a variety of operationIds, such as the following examples and patterns:

  • getAccountsUsingGET
  • myMethodNameHere
  • operation1, operation2, operation3, …

However, most operationIds don’t even exist within an OAS specification. This is a lost opportunity.

The operationId provides an opportunity to be clear about the operation capability. Rather than coupling it to your code and leaking your implementation details, name it using the capability or feature it supports. For example:

For example:

paths:
  /books:
    get:
      operationId: ListBooks

Using this naming scheme, the OAS description is easier to read and follow when an operation is being referenced from a Link object using the name, rather than an OperationRef.

Below is an example (using OAS 3.0) of referencing the operation above from a Link object:

links:
  bookList:
    operationId: ListBooks

Which is much easier to read than using an OperationRef, where ~1 is an escaped forward-slash:

links:
  bookList:
    operationRef: '#/paths/~1v2~1books~1/get'

This operation reference would resolve to the path /v2/books using the get HTTP method under the path. If the path or verb were to change during the early stages of API design, the reference would no longer be valid. Note that operationRef, unlike the operationId, may use an external reference and is necessary when referencing operations outside of the immediate document.

4. Write a concise and complete operation summary

Examples of poor quality operation summaries include:

  • Get books
  • Update the account
  • Integrates with the DND system
  • This endpoint/operation …

A better example might be:

paths:
  /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results

This is a short, concise description of the operation, starting with a verb, that helps the reader understand what is possible immediately. It also makes it easier to perform a “find in page” search to jump specifically to the desired operation, without knowing the path or resource name. Plus, most rendering tools, such as SwaggerUI, will render the summary when the operations are collapsed, making it easier to understand what each operation does at a glance.

5. Write a complete operation description

An operation description is an opportunity to turn the machine-readable details of your operation, such as required and optional parameters, into human readable form.

Below is an example of a better operation description, that includes information about pagination support and default sort order:

paths:
  /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results
      description: |
        Provides a paginated list of books based on the search criteria provided. If no search criteria is provided, books are returned in alphabetical order. 

Even better, offering a few example use cases can help a developer get started even faster:

paths:
  /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results
      description: |
        Provides a paginated list of books based on the search criteria provided. If no search criteria is provided, books are returned in alphabetical order. 

        Example: Searching for a book by title

        GET books?q=RESTful

        Response:

        { 
          "books": [
            { "bookId": "abc123", "title": "RESTful Web Clients", ... }, 
            ...
          ]
        }

I truncated the example for brevity, but offering a complete response example within the operation docs is useful for those becoming familiar with your API.

6. Document all query arguments, parameters, and schema objects

As the details of each operation become more concrete, use the opportunity to fully capture the details about query arguments and parameters. This includes things such as:

  • The name and type
  • A clear description, including a human-readable summary of any specific format rules or restrictions
  • Min/max values, defaults when the value isn’t provided, and other related details

Below is an example using these principles:

paths:
  /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results
      description: |
        Provides a paginated list of books based on the search criteria provided. If no search criteria is provided, books are returned in alphabetical order. 

        Example: Searching for a book by title

        <truncated for space>

      parameters:
        - in: query
          name: q
          type: string
          description: A query string to use for filtering books by title and description. If not provided, all available books will be listed. Note that the query argument 'q' is a common standard for general search queries
        - in: query
          name: daysSinceBookReleased
          type: integer
          description: A query string to use for filtering books released within the last number of days, e.g. 7 means in the last 7 days. The default value of null indicates no time filtering is applied. Maximum number of days to filter is 30 days since today
        - in: query
          name: offset
          type: integer
          default: 0
          minimum: 0
          description: A offset from which the list of books are retrieved, where an offset of 0 means the first page of results. Default is an offset of 0
        - in: query
          name: limit
          type: integer
          default: 25
          maximum: 100
          minimum: 1
          description: Number of records to be included in API call, defaulting to 25 records at a time if not provided

Notice how each description summarises the details defined in OAS, making it easy to understand without needing to interpret the YAML.

Ensure your schema objects apply the same kind of thoughtfulness:

definitions:
  ListBooksResponse:
    description: | 
      A list of book summaries as a result of a list or filter request
    type: object
    properties:
      books:
        type: array
        items:
          $ref: '#/definitions/BookSummary'

Notice how I use the operationId in the definition name: ListBooksResponse. This makes it easier to read when first reviewing the YAML, as well as when later editing the file. I also try to capture any domain-specific rules regarding cardinality in the description for quick and easy understanding.

7. Capture all operation success and error response codes

A client may not always experience success when calling an operation. Therefore, we need to capture the success and error codes that the operation may return, including details about the error response and why it may have happened. This helps educate those new to HTTP.

Below is an example that demonstrates a 200 success code, plus a 401 and 403 response:

 /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results
      description: <truncated for space>
      parameters:
        <truncated for space>
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/ListBooksResponse'
        '401':
          description: >-
            Request failed, This is the response received when a request is made
            with invalid API credentials
        '403':
          description: >-
            Request failed, This is the response received when a request is made
            with valid API credentials towards an API endpoint or resource you
            do not have access to.

8. Define a single error response schema

Your operations will need to return an error response when something is incorrect about the request or a failure occurs on the server. Rather than defining a unique error response for each operation, craft an error response format that may be used for each operation. This allows clients to parse one error response format, rather than needing to handle the parsing of a custom error response payload for each operation or response type. It also makes it easier to reliably concert the error response into a user-readable format.

Rather than creating your own error response format, I recommend using RFC 7807: “Problem Details for HTTP APIs”. It is simple to understand and may be evolved to handle more complex errors. Zalando has even gone to the effort of capturing the problem details format as an OAS YAML description, which you can paste into your API description or reference remotely:

 /books:
    get:
      operationId: ListBooks
      summary: List books available in the book store for browsing, with filtering support to narrow the results
      description: <truncated for space>
      parameters:
        <truncated for space>
      responses:
        '200':
          description: Success
          schema:
            $ref: '#/definitions/ListBooksResponse'
        '401':
          description: >-
            Request failed, This is the response received when a request is made
            with invalid API credentials
          content: 
            application/problem+json:
              schema:
                $ref: 'https://opensource.zalando.com/problem/schema.yaml#/Problem'
        '403':
          description: >-
            Request failed, This is the response received when a request is made
            with valid API credentials towards an API endpoint or resource you
            do not have access to.
          content: 
            application/problem+json:
              schema:
                $ref: 'https://opensource.zalando.com/problem/schema.yaml#/Problem'

If you currently have operations that use a mixture of error response formats, or you want to add support for the problem details media type, encourage your clients to include an Accept header that includes application/problem+json when they want to use the RFC 7807 format, defaulting to your current error response when not provided by the client.

Note that XML and JSON are supported by the RFC, so those supporting both media types or using XML instead of JSON can benefit from this RFC.

9. Verify all paths, query arguments, parameters, and schema definitions are consistent

Before you finalise your API design, go through all paths, query arguments, parameters, and schema definitions and ensure they match your naming standards. Also ensure that you haven’t mixed styles, such as camelCase vs under_score. These kinds of things can slip into your API description due to copy-and-paste or migrating to a new set of API design standards. Once they slip in and the API is pushed to production, it is too late to change them and will forever remind you of missing this step! Be sure to read our schema stitching vs federation blog post!

10. Link to external documentation for additional help

Finally, review your API description and look for areas where you can link to other areas of your API documentation. Consider links to resources such as:

  1. The getting started guide
  2. How to use your API’s authentication and authorisation properly
  3. Detecting rate limit thresholds through headers and/or response codes
  4. Common patterns across your API, e.g. pagination, sorting, error responses, exceeding rate limits and backoff strategies, etc.
  5. Helper libraries and SDKs that can make development faster

Wrap-up

Designing your API goes far beyond just choosing patterns, such as CRUD, and payload structures. It is important to review every aspect of your API description, including documentation, to ensure that developers can integrate with your API quickly and easily – this being the beauty of using OpenAPI. It is also a chance to catch inconsistencies in how you handle errors and other recurring patterns across this and other APIs in your portfolio.

Use the developer experience (DX) review process as a chance to coach others and to prevent problems before your API goes live. If you need more insight about how to conduct a DX review, check out my article titled, “How to conduct an API design review” for further tips and tricks for a productive and healthy DX review session.