Creating a Simple OpenAPI Spec
For the impatient: if you just want to look at an example without any additional context, there’s a spec here https://github.com/lornajane/flask-planets-and-webhooks/blob/master/openapi.yaml
About the API
I’ve been using a very trivial API example project in a few of my recent talks on API-adjacent topics, with just a couple of endpoints. There’s a blog post about the Planets API but in short you can get a list of either one planet or many, with requests like:
GET http://localhost:5000/planets
GET http://localhost:5000/planets/3
Now you have a decent grasp of what this thing does, I’ll start the description.
Create File, Configure Tools
I’ll be using YAML, you can use JSON instead if you prefer. My file is called openapi.yaml
which is a reasonably common file name but you can name your file what you like.
I never attempt to create a file like this without some sort of validation tool. Something like Stoplight Studio will do this for you, but I mostly use my usual editor so I’m using Stoplight’s standalone open source linting tool spectral combined with watch, something like this:
watch -n 3 spectral lint openapi.yaml
Spectral is fairly Opinionated (with an intentional capital O) but it’s a decent starting point.
With this in place, I started adding a few lines to the file:
openapi: 3.0.3
info:
title: Planets and Webhooks Demo API
version: 0.0.1
description: Simple flask-backed API showing some example API endpoints and with webhook debugging features.
contact:
name: Lorna Mitchell
url: https://github.com/lornajane/flask-planets-and-webhooks
email: [email protected]
servers:
- url: http://localhost:5000
description: Sample project running locally
externalDocs:
description: GitHub project for the code and information about this API
url: https://github.com/lornajane/flask-planets-and-webhooks
You’ll probably still see some validation errors, in this version of OpenAPI 3.0.3 it’s required to have a paths:
item at the top level, but you get the idea of how the start of the file should look.
Add API Endpoints
This API has two endpoints, so it’s a nice, small problem space to try to describe! The /planets
route returns JSON, an array of objects where each object describes a planet. The /planets/{id}
route returns just one object, but it is identical to the object returned by the collection. OpenAPI supports a special $ref
field for reusing definitions across different parts of the spec. You can use it to structure your spec in a more readable way – but its superpower is in making sure that things that are the same are only defined once, making it easier to keep everything consistent and maintainable.
I prefer to start with an endpoint without any reuse, then refactor to components later. As an example, here’s the /planets/{id}
endpoint in its first iteration:
paths:
/planets/{planetId}:
get:
operationId: onePlanet
summary: Fetch one planet by position
description: Get the data relating to one planet
parameters:
- name: planetId
in: path
required: true
schema:
type: number
example: 4
responses:
'200':
description: Planets in a list
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: Name of planet
example: "Saturn"
position:
type: number
description: Order in place from the sun
example: 6
moons:
type: number
description: Number of moons, according to NASA
example: 62
I did say it was verbose! The value is in the detail though – every tool that consumes this description has all this information and examples available.
I know I’ll need the actual planet object again though, so here’s the same sample again, refactored to put the planet object into components
as a reusable element:
paths:
/planets/{planetId}:
get:
operationId: onePlanet
summary: Fetch one planet by position
description: Get the data relating to one planet
parameters:
- name: planetId
in: path
required: true
schema:
type: number
example: 4
responses:
'200':
description: Planets in a list
content:
application/json:
schema:
$ref: "#/components/schemas/planet"
components:
schemas:
planet:
type: object
properties:
name:
type: string
description: Name of planet
example: "Saturn"
position:
type: number
description: Order in place from the sun
example: 6
moons:
type: number
description: Number of moons, according to NASA
example: 62
With the reusable component ready, have a look at how the collection looks using it:
paths:
/planets:
get:
operationId: allPlanets
summary: List all planets
description: Returns a list of all the planets that are stored in the system.
responses:
'200':
description: Planets in a list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/planet"
It’s a little different as it’s an array of objects, but we can reuse that single object as an array item.
Documentation as Output
There are so many things you can do with an OpenAPI spec but a very human-centric one is to generate documentation. There are a few options for this, again Stoplight Studio has it built in, but I mostly use open source tool ReDoc – their 2.0 release isn’t official yet but it is great! Importantly, the project was very receptive to, and quick to fix, the accessibility issues I logged.
Generating some documentation as you go along can help show how things are going when you come to describe your own APIs.
Final Touches
If you’re playing along at home, you’ll notice that spectral is still having Opinions! That’s because we haven’t added tags yet. Tags aren’t that useful on an OpenAPI spec this size, but for larger APIs they are an excellent innovation. The previous yaml example showing the collection endpoint has a tag, and to finish, all I need to show you is the tags:
element in the spec:
tags:
- name: planets
description: API containing solar system data
For tags, parameters, respond fields, endpoints, and really everything else, the descriptions and examples are at least as important as the field name and data type information! Not all of us are talented writers but the best specifications are those that do include these extra-value items – the improved Developer Experience (or as I like to call it “developer delight factor”) is absolutely worth it!
Let me know if you found this post useful or if there’s anything more detailed you’d like to see covered. I’m still figuring a bunch of these things out myself but I’m happy to share :)
Also published on Medium.
When I try your content in swagger editor I am getting 403 forbidden
I’m not sure what’s happening there, I can load the files in my browser even when I’m not logged in.
Thanks for your blog. ;)
I would like to request something, though.
A thing that I saw in the OAI site (https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore-expanded.yaml#L138) and I can’t find an explanation anywhere is the use of two Schemas Pet and NewPet. It seems that it is a solution to deal with a required id on post and put. Perhaps you could write a post about this practice ?