OpenAPI 3.2 is finally here.
It’s been such a long time, we were all dried up, but now the rains are here.

I love that new feature smell

  • 40,248 hours, or
  • 1,677 days, or
  • 55 months, or
  • 4 years.

That’s how long we had to wait between OpenAPI 3.1 and OpenAPI 3.2.

Was it worth it? Let’s find out!


Upgraded tags

The Tag object can now operate hierarchically:

  • summary field to allow additional metadata to be supplied for a tag.
  • parent field that points to the parent tag.
  • kind field that provides the ability to categorize or group tags.

The kind field is freeform, but really the goal is to establish a registry of known types like nav or badge.

Tags are a really important part of the specification. They are of little use to some folks, and to others, they form the foundation of metadata used to group and navigate the surface of the API.

I am really excited about this upgrade because with nested tags comes a much stronger ability to organize operations and create a custom and specific information architecture for our APIs.


ALL THE METHODS!

There is a new kid on the block when it comes to HTTP methods. QUERY is a new method that allows us to submit a body as an idempotent, read-only operation.

In English, this means instead of using a hugely complex encoded query parameter for a GET request, that query object can be passed as a request body using the QUERY method. It’s a read-only method but allows big, complex objects.

The QUERY method can now be used alongside all the other favorites.

But wait… there’s more

A new additionalOperations property now exists, which allows you to ADD YOUR OWN METHODS.

Want a new SPICEGIRLS method? No problem. What about a new CHEESE method?

paths:
    /crackers:
        additionalOperations:
            CHEESE:
                description: cheddar.
                ...  
    /zig-a-zig-ahh:
        additionalOperations:
            SPICEGIRLS:
                description: you gotta get with my friends.
                ...

And you know what? It gets even better. If you want, you can JUST ADD THEM INTO THE ROOT with libopenapi! you can bypass additionalOperations (but you shouldn’t for the sake of correctness).

paths:
    /crackers:
        CHEESE:
            description: cheddar.
            ...  
    /zig-a-zig-ahh:
        SPICEGIRLS:
            description: you gotta get with my friends.
            ...

You can do this in libopenapi, but it’s not a part of the standard and the correct way to do things is to use additionalOperations in order to maintain compatibility with other tools and the standard.


Document resolution and identity

A personal nightmare for me in the past. Literally, I would wake up exhausted in the morning after having nightmares about being stuck in recursive loops, trying to resolve references that loop around endlessly.

Well, some of these issues just got a little easier with the introduction of the $self property on the entry document.

The purpose of this new property is to create a ‘resolution’ starting point for references, as well as identify the document with a unique URI.

Essentially $self marks the location of the entry point of the document. All relative references in the document should resolve from this point.

For example:

$self: https://api.pb33f.io/wiretap/giftshop-openapi.yaml

This tells the tool to resolve any relative references (from this root document) to resolve from /wiretap on the domain api.pb33f.io.

libopenapi already supported this capability, but now there is a standard for it!


Streaming support

Sequential media, server-sent events (SSE), and other streaming media types like text/event-stream, application/jsonl, and application/json-seq are now officially supported.

When it comes to responses that operate on lines or sequences, there are new itemSchema, itemEncoding, and prefixEncoding properties in the MediaType object that allow each item to be described correctly.

More encoding types allow more control over multipart media types versus a singular encoding property.

Streaming is useful for APIs that send chunked or real-time data that may not all arrive in one blob, or may be collections of many different objects. Traditionally, we have used WebSockets or SSE for these types of interfaces, but there are other options.

My mate, Phil Sturgeon, has written a great article on JSON Streaming in OpenAPI 3.2.


A new in location for the Parameter object called queryString.

  openapi: 3.2.0
  paths:
    /searchStore:
      get:
        parameters:
          - name: productQuery 
            in: queryString
            required: true
            content:
              application/x-www-form-urlencoded:
                schema:
                  type: object
                  properties:
                    filters:
                      type: object
                      properties:
                        category:
                          type: string
                        price_min:
                          type: number
                        price_max:
                          type: number
                    sort:
                      type: string
                    limit:
                      type: integer

Essentially, it allows the entire query to be parsed as a single field versus individual parameters. Also, allowReserved is now available on any Parameter with an in property set.

Cookie parameters can now use the style property to define cookie content using a semicolon as a delimiter.


Look who’s back, back again

XML is back…

It’s resurfacing again because AI is using it under the covers to build prompts.

What goes around comes around, and there is a new nodeType added that allows mapping to common XML node types like element, attribute, text, cdata, or none.

attribute and wrapped have become deprecated in favor of using a nodeType like nodeType: attribute or nodeType: element.


Upgraded examples

We all love them and need them. Now there are two new fields, dataValue and serializedValue, in any object that supports examples

  • dataValue is the structured format of the example.
  • serializedValue is how the example will look when it’s handled by the API.

Here are a couple of examples I just made up.

openapi: 3.2.0
paths:
    /api/data:
      get:
        parameters:
          - name: X-Filter-Options
            in: header
            style: simple
            explode: false
            schema:
              type: object
              properties:
                include:
                  type: array
                  items:
                    type: string
                exclude:
                  type: array
                  items:
                    type: string
            examples:
              filter_header:
                summary: Complex filter in header
                description: Shows how objects are serialized in headers
                dataValue:
                  include:
                    - posts
                    - comments
                  exclude:
                    - draft
                    - deleted
                serializedValue: "include,posts,comments,exclude,draft,deleted"
openapi: 3.2.0
paths:
    /search:
      get:
        parameters:
          - name: tags
            in: query
            style: form
            explode: true
            schema:
              type: array
              items:
                type: string
            examples:
              exploded_form:
                summary: Form style with explode=true
                dataValue: ["urgent", "bug", "high-priority"]
                serializedValue: "tags=urgent&tags=bug&tags=high-priority"

          - name: categories
            in: query
            style: form
            explode: false
            schema:
              type: array
              items:
                type: string
            examples:
              comma_separated:
                summary: Form style with explode=false
                dataValue: ["electronics", "computers", "accessories"]
                serializedValue: "categories=electronics,computers,accessories"

          - name: keywords
            in: query
            style: spaceDelimited
            schema:
              type: array
              items:
                type: string
            examples:
              space_delimited:
                summary: Space delimited array
                dataValue: ["bish", "bash", "bosh"]
                serializedValue: "keywords=bish%20bash%20bosh"

          - name: ids
            in: query
            style: pipeDelimited
            schema:
              type: array
              items:
                type: integer
            examples:
              pipe_delimited:
                summary: Pipe delimited integers
                dataValue: [1, 2, 3, 4, 5]
                serializedValue: "ids=1|2|3|4|5"

Security updates

The main update is that the OAuth2 Device Authorization Flow is supported by the addition of the new deviceAuthorization field in the flows object.

For individual flows, there are new deviceAuthorizationUrl and tokenUrl properties.

A new oauth2MetadataUrl security scheme property has been added that defines the URL used for authentication server metadata.

Schemes can now also be deprecated.


Media Types added to Components

MediaType objects can now be added to the new mediaTypes section in the Components model, allowing clean re-use and referencing of common media types used throughout the API.


Additional feature roundup

There are a number of smaller, but no less important, updates:

  • Server object now supports name property.
  • Formal ABNF syntax supported in URLs.
  • The propertyName discriminator value is now optional.
  • New defaultMapping added to discriminator that defines the schema to be used in case propertyName is unset or invalid.

vacuum v0.18+ and libopenapi v0.28+ both support OpenAPI 3.2 and all of the features mentioned above.

Check out OpenAPI 3.2 release notes and the 3.2 Upgrade Guide to learn more about the spec updates and read more of the clarifications made across the board.