Understanding OpenAPI 3.0 Extensions

Last Updated: 2021-06-12 10:56

While the OpenAPI Specification tries to accommodate most use cases, additional data can be added to extend the specification at certain points.

OAS, v3.0

Introduction

This is the first in a (planned) multi-part series on OAS 3 - the OpenAPI 3.0 Specification. In this post, I’d like to introduce the reader to what I believe is one of OpenAPI’s most powerful features, Specification Extensions.

Prerequisites

This post assumes basic familiarity with OpenAPI 3 - specifically, it is assumed that the reader has at least a basic understanding of:

(Need a refresher?)

These resources should get you up to speed!

What are Extensions?

Extensions are just extra data that you’re allowed to include in an OpenAPI spec which have no inherent semantic value. The specification allows API authors to attach specially formatted keys to specific object types, in order to provide consumers with additional metadata.

Here is an example of some extension data added to an Operation Object:

#...
paths:
  /stuff
    get:
      operationId: getStuff
      x-my-extension: >
        The value here can actually be any valid JSON data that suits you!
        I picked a string because it let me sneak some more
        prose into a block of code. :)
      content:
      # ...

That might not seem extremely useful at first glance, but the implications for system design and integration are rather profound. Hang in a bit, and I’ll get you convinced, okay? Okay!

The Specifics

The field name MUST begin with x-, for example, x-internal-id. The value can be null, a primitive, an array or an object. Can have any valid JSON format value.

OAS, v3.0

Extensions can be placed on almost every object type in the OAS 3 spec (see this page for a comprehensive list). Beyond that, there are no limitations placed on the extension data.

What are they for?

Extensions can be used to supplement the structural portrait of an API with metadata that can be used to interpret that structure.

The OAS spec provides a mechanism for API authors to define, unambiguously, what paths their APIs provide, what operations can be performed on those paths, authentication and authorization information for a variety of common auth schemes, API parameters and the rules for their serialization, and the structure of requests/responses in comprehensive detail. All of the information is human and machine readable and can be used to generate server stubs and full-featured API clients.

…So, what’s missing? Meaning!

In essence, a spec defines the syntax of your API. Extensions allow you to define the semantics - providing API clients with guidance on how to leverage the structure of your API in the context of procedural- or business-logic.

“Oh, neat, Andrew, that sounds lofty and wonderful, but what does all that mean, and how can I use it to solve real-world problems!?

Fair enough. First, let’s have a quick peek at some samples, then we’ll talk about what extensions are not. Afterwards, we’ll dig into an example or two to really hammer the utility of the thing home.

Okay? Okay!

Examples

What are they not?

OpenAPI extensions provide API consumers with extra data about an API, not about what’s in an API.

Note that in all of the examples above, the extensions don’t declare anything that is sent to or received from the API; instead, they provide context that is used to intepret things that are sent to or received from the API. Anything that is actually sent or received by an API and can be described by facilities in the spec, should be described by the spec!

You could, for example, add an x-my-content-type annotation to a ResponseObject in order to indicate the content type returned, but this is a bad use of specification extensions: HTTP already provides a way for us to relay this information to a client (Content-Type); The OpenAPI spec has facilities to indicate to clients what range of content types an endpoint produces and the format of the response on a content-type-by-content-type basis.

A Simple Example

Let’s suppose you’ve got a number of API’s up and running, and they’re already being used by a large number of clients in production. Let’s further suppose that some new tool is being developed at your company, an inventory system. This system is supposed to be able to list all of the entities in a given API. The design requirements indicate that the UI should have just two fields for each entity in a given API, Unique ID and Type.

You have OAS 3 specs for all of your API’s, so the Type field is no problem: the people building the inventory tool will just leverage the type name from the components section of your OAS 3 API description. But the Unique ID bit is a little more complicated: some of your API’s use an integer ID to uniquely identify resources (an auto-incrementing field from the backing DB, perhaps), some of your API’s use UUID’s, and some of them use URI’s. Worse, each of the API’s uses a different name for their resource ID’s.

What to do?

There are a handful of approaches we could take here:

  1. ❌ Just ensure the documentation is up to date and punt the issue to the inventory team:
    • Requires another team to maintain some mapping of API endpoints to unique ID fields.
  2. ❌ Change the API’s to have consistent ID types and names2:
    • Requires all of your API maintainers and all of their clients to update their code.
    • Breaks backwards compatibility without adding any new functionality.
  3. Relay that information via extension data and call it a day!
    • No changes to the API, itself! (Just update the spec!)
    • No changes to the payload format!

Comparing Approaches

Not an Extension

Here, the structure of MyResource has changed. The API and clients will require code changes to handle the new id field. Documentation will have to be written separately. The resources now have more than one unique id:

schemas:
  MyResource:
    type: object
    properties:
      # All API's must now provide
      # a UUID "id" field:
      id:
        type: string
        format: uuid
      # Backwards compatibility:
      resourceId:
        type: integer
        format: int64
      # ...

Notes

  • The id field is in the response body.

Using An Extension

Here, MyResource remains unchanged. The response object, is now annotated with the extension x-unique-id - which can be optionally leveraged by the inventory tool team (and clients and doc generators!):

schemas:
  MyResource:
    type: object
    # Indicate that the id field
    # is unique without changing
    # the payload. Doc generators
    # can use the annotation too!
    x-unique-id: resourceId
    properties:
      resourceId:
        type: integer
        format: int64
      # ...

Notes

  • The x-unique-id field is not in the response body.

Further Reading

If you got a kick out of this, I strongly encourage you to read:


  1. Alternatively, swaggerhub has a single-page OpenAPI 3 tutorial here that you could check out! 

  2. (Admittedly, this is preferable from an API design perspective, but requires roadmap alignment across multiple dev teams and has a larger impact on delivery date - with the same business value add).