How should we mark endpoints as required in the OpenAPI Spec?

I’m seeking input from people if you have an opinion on the technical method we use to mark HSDS API endpoints as REQUIREd in the OpenAPI.json file. This is to a) support future validation tooling producing a compliance report and b) bringing the OpenAPI file in line with the current specification.

Currently, the only REQUIRED endpoints in the specification are:

  • /
  • /services
  • /services/{id}

The contradiction I’m trying to resolve is that OpenAPI is really designed for describing a single instance of an API, rather than prescribing how an API should conform to a specification. Therefore it lacks features such as marking endpoints as required.

This has the effect that anyone trying to implement API validation needs to maintain a separate list of the endpoints required by the HSDS Specification, in order to decide whether the overall API is conformant or not i.e. “your endpoints are all producing valid data, but you lack the /services/{id} endpoint so sadly you’re not compliant with the specification”

I noticed that the CommonGrants folk got around this by using OpenAPI’s Tag object, and tagged particular endpoints with required.

An alternative way to do this is to extend OpenAPI’s specification with a custom field which would mark the endpoint as required. This is supported by OpenAPI’s specification:

Both approaches are similar in terms of pragmatics. We’d either create a required tag in our OpenAPI specification docs and apply it to endpoints, or we’d create a x-required: true property and apply it to endpoints.

See also the Github issue I raised on this:

I ran this by our old friend Kin Lane, API Evangelist and OpenAPI expert. He says:

“Your three options are 1) Tags, 2) Extensions, 3) Separate OpenAPIs for required or additional paths / operations and being explicit in the description.”

1 Like

Been meaning to respond to this for a while!

Great to have this input and glad to see us on the right track already. I hadn’t considered splitting the required and non-required endpoints into separate openapi.json definitions. There is an elegance in that simplicity; “everything defined in this file is required, everything in the other file is optional”.

My only hesitation with that option is for Profiles, where I think HSDS Schema tools used to generate the Profiles assume a single openapi file. But maybe the tools are actually flexible enough to handle splitting it into two; they actually just match based on names and calculate the diffs, so it’s reasonable that this could work out of the box.

This leaves us with three options now! Keen to hear others’ thoughts on this, as once we have something of a direction I can spin up a proposal doc to get in front of the committee.

Hey @mrshll,

Thanks for flagging this thread for me! As we talked about a few weeks ago, CommonGrants grappled with a similar question regarding our OpenAPI spec. As @bloom mentioned the three main options we considered were: 1) Tags, 2) Extensions, 3) Separate OpenAPI docs.

We ultimately chose tags, because it:

  1. Worked well with existing OpenAPI tooling without customizations (e.g. Swagger docs, swagger-parser)
  2. Allowed us to support multiple statuses (e.g. required, optional, experimental)
  3. Allowed us to maintain a single OpenAPI spec per version
  4. Allowed adopters to reference a single spec for implementation and validation

The tool-compatibility and single OpenAPI spec were strong motivators because we have a CLI tool that checks APIs against our spec that would be a lot more complicated to implement if we had either a custom extension or multiple specs per version.

This is good motivation to add this decision to our ADRs in the next round of website updates as well. Here’s the draft ADR for that decision below:

Decision

As described in our specification, we chose to use tags to categorize API routes into three main categories:

  • Required: Stable endpoints that adopters MUST implement
  • Optional: Stable endpoints that adopters MAY implement
  • Experimental: Non-stable endpoints that adopters MAY implement to provide feedback

Positive consequences

  • Allows us to use the standard OpenAPI rendering tools like Swagger, RapiDoc, etc. without any customizations
  • Allows us to use standard OpenAPI parsers like swagger-parser without any customizations
  • Allows us to validate API implementations against a single spec, instead of multiple
  • Only requires maintaining or updating a single spec per API version
  • Enables OpenAPI spec consumers to understand the meaning of required status from the OpenAPI spec itself

Negative consequences

  • Causes routes to be listed twice (may be a feature or a bug depending on how you look at it)
  • Overloads the use of tags in the OpenAPI spec

Criteria

  • Machine-readable: Clearly identifies route status in a machine-readable way.
  • Easy-to-render: Clearly identifies route status in the human-readable rendering of OpenAPI spec (e.g. via Swagger docs)
  • Tool compatible: Recognized by and compatible with most standard OpenAPI tooling (e.g. swagger-parser)
  • Maintainable: Easy to maintain and version alongside other API changes over time.
  • Adopter DX: Easy for adopters to reference a single spec when consuming, implementing, or validating against the protocol.
  • Self documenting: Clearly documents how to interpret required status in the OpenAPI spec itself.
  • Scalable: Easily supports additional statuses in the future, if needed.

Options considered

  • Tags
  • Extensions
  • Separate specs

Evaluation

Side-by-side comparison

Criteria Tags Extensions Separate specs
Machine-readable :white_check_mark: :white_check_mark: :white_check_mark:
Easy-to-render :white_check_mark: :cross_mark: :white_check_mark:
Tool compatible :white_check_mark: :cross_mark: :white_check_mark:
Maintainable :white_check_mark: :white_check_mark: :cross_mark:
Adopter DX :yellow_circle: :yellow_circle: :cross_mark:
Self documenting :white_check_mark: :yellow_circle: :white_check_mark:
Scalable :white_check_mark: :white_check_mark: :cross_mark:

Tags

Use the standard tags field in the OpenAPI spec to categorize routes into three main categories:

  • required (e.g. tags: [required])
  • optional (e.g. tags: [optional])
  • experimental (e.g. tags: [experimental])

Each tag would have a description that explains the meaning of the tag.

Bottom line: This is the best option if:

  • We want to identify route status in a self-documenting way that is compatible with standard OpenAPI rendering and parsing tools,
  • But we’re okay with overloading the use of a standard OpenAPI spec feature.

Pros

  • Supported by standard OpenAPI rendering tools like Swagger, RapiDoc, etc. (e.g. Swagger UI will render tags as a collapsible section)
  • Supported by standard OpenAPI parsers like swagger-parser (e.g. swagger-parser will parse tags as a list of strings)
  • Enables us to validate API implementations against a single spec, instead of multiple
  • Only requires maintaining or updating a single spec per API version
  • Self-documenting - Descriptions of tags can be used to explain the meaning of required status
  • Scales well to support additional statuses in the future, if needed

Cons

  • Causes routes to be listed twice (may be a feature or a bug depending on how you look at it)
  • Overloads the use of tags in the OpenAPI spec
  • Each tag section might get cluttered if we have a lot of routes

Extensions

Create a new extension field x-status in the OpenAPI spec to categorize routes into three main categories:

  • required (e.g. x-status: required)
  • optional (e.g. x-status: optional)
  • experimental (e.g. x-status: experimental)

Each status would have a description that explains the meaning of the status.

Bottom line: This is the best option if:

  • We want to identify route status in a machine-readable way that avoids overloading a standard OpenAPI spec feature,
  • But we can compromise on direct compatibility with standard OpenAPI tooling or are willing to add custom tooling to support it.

Pros

  • Allows us to validate API implementations against a single spec, instead of multiple
  • Only requires maintaining or updating a single spec per API version
  • Scales well to support additional statuses in the future, if needed
  • Avoids overloading the use of tags in the OpenAPI spec

Cons

  • Not easily supported by standard OpenAPI rendering tools like Swagger, RapiDoc, etc.
  • Not easily supported by standard OpenAPI parsers like swagger-parser
  • Not as self-documenting as tags or separate specs, unless we were to add a description attribute to the extension field

Separate specs

Create a separate spec for each status.

Bottom line: This is the best option if:

  • We want to clearly separate routes by status while still being able to use standard OpenAPI tooling without overloading standard OpenAPI spec features,
  • But we’re willing to accept the overhead of maintaining multiple specs per API version asking adopters to reference multiple specs for implementation or validation.

Pros

  • Supports all standard OpenAPI tooling without customizations (e.g. each spec can be rendered independently)
  • Avoids overloading the use of tags in the OpenAPI spec
  • Self-documenting - Each spec’s top-level description can be used to explain the meaning of required status

Cons

  • Requires maintaining multiple specs per API version
  • Requires adopters to reference multiple specs for during implementation or validation
  • Becomes exponentially more complex to maintain as the number of statuses increases
1 Like

Hey @billy.daly, thanks so much for taking the time out of your day to contribute to this thread! That means a lot; especially considering the depth of your response!

I think you’ve convinced me that Tags are in fact the best option for us now. In particular the support for multiple statuses, although I appreciate you outlining how we could frame an extension to support this as well, and thus avoiding the Boolean trap!

The sell of the Tags approach to work with tooling without modifications is something that I think is the most important at the moment. Our docs uses a sphinx plugin which gives us quite tight control over rendering individual routes, but I know quite a lot of our community like Swagger pages and it would be good to have the Tags supported here too.

My only concern would be overloading the use of Tags in OpenAPI, which you highlight. However, our spec doesn’t currently use Tags at all so I think this won’t be an issue for us in the near-to-medium term. If it ever become an issue, we can make plans for changing the approach in a future MAJOR version to make room for other use of Tags.

So the bottom line at the moment: I think we should be using Tags for the reasons you outline and I see benefit in outlining our Tags to yours. In practice I think we’d only use the required and optional Tags, but it’s good to know that experimental is there and already in use in another spec, should we ever see the need for it.

Unless I hear from others in this thread — or on Github — that they have concerns with the use of Tags as opposed Extensions or separate OpenAPI files, I’ll write this up as a proposal for the next version for the HSDS Specification and check you’re ok being added as an author.

1 Like