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 operationId
s, such as the following examples and patterns:
- getAccountsUsingGET
- myMethodNameHere
- operation1, operation2, operation3, …
However, most operationId
s 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:
- The getting started guide
- How to use your API’s authentication and authorisation properly
- Detecting rate limit thresholds through headers and/or response codes
- Common patterns across your API, e.g. pagination, sorting, error responses, exceeding rate limits and backoff strategies, etc.
- 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.