OpenAPI Tutorial Summary
OpenAPI is a specification for describing RESTful APIs. I found a tutorial here. For my knowledge, I wanted to summarize what I learned from walking through this tutorial. It’s nice to have an API description. Without one, you have to read through other people’s code. If you only have a website address, guessing what to send is a chore. There are tools out there to make this simpler. Writing the YAML by hand can help you understand the specification. You can edit a specification interactively at the swagger editor website. Recently the OpenAPI initiative announced the availability of OpenAPI 3.1. If you are using OpenAPI 3.0.x you can learn about migrating. As of this posting, openapi.tools site shows no tools using 3.1. This past week, those at my company agreed to update to using 3.0.x. An interesting note of the difference between 3.0.x and 3.1 is the better JSON schema consistency. This summary focuses on 3.0.3.
OpenWeatherMap API Specification
You can visit the swagger editor and plop the whole specification into the editor to view the generated API documentation.
There are a few parts of the specification that are simple and easy to understand.
- openapi spec version
- describes what version of the specification is being described
- info
- self-description of the API, version, who to contact, terms of service, and license
- tags
- a way to group the API paths
- externalDocs
- another place to visit for more information about using the API
- servers
- what addresses to reach out to for sending requests
- security
- what authorization is required when interacting with the API
Then we are going to cover the more complicated topics.
- paths
- the routes and their operations
- components
- parts of the API that might be duplicated or reused
openapi specification version
# In the linked example the specification is 3.0.2
openapi: 3.0.3
API tooling uses this attribute to know what specification to validate.
info object
info:
title: "OpenWeatherMap API"
description: "Get the current weather, daily forecast for 16 days, and a three-hour-interval forecast for 5 days for your city. Helpful stats, graphics, and this day in history charts are available for your reference. Interactive maps show precipitation, clouds, pressure, wind around your location stations. Data is available in JSON, XML, or HTML format. **Note**: This sample Swagger file covers the `current` endpoint only from the OpenWeatherMap API. <br/><br/> **Note**: All parameters are optional, but you must select at least one parameter. Calling the API by city ID (using the `id` parameter) will provide the most precise location results."
version: "2.5"
termsOfService: "https://openweathermap.org/terms"
contact:
name: "OpenWeatherMap API"
url: "https://openweathermap.org/api"
email: "some_email@gmail.com"
license:
name: "CC Attribution-ShareAlike 4.0 (CC BY-SA 4.0)"
url: "https://openweathermap.org/price"
tags
Tags are great for grouping sets of paths and make it clearer in the Swagger UI what a grouping of paths does.
tags:
- name: Current Weather Data
description: "Get current weather details"
externalDocs
This is easy to understand you probably have a lot more documentation lurking somewhere about your API and you can point consumers of the API to that location.
externalDocs:
description: API Documentation
url: https://openweathermap.org/api
servers
The listing of servers can be simple or as complex as you need you can read the docs for more information.
A simple list of URLs.
servers:
- url: "https://api.openweathermap.org/data/2.5"
You can get more complicated by using templating and variables. You can also override servers at the path level if you were splitting the functionality of an API away from a specific server.
servers:
- url: https://{environment}.example.com/v2
variables:
environment:
default: api # Production server
enum:
- api # Production server
- api.dev # Development server
- api.staging # Staging server
security
Your API might have no security, use an API key, or another authentication scheme. This gets a little more complicated and introduces a new concept I have not discussed yet called components. When openapi 2.0.0 changed to 3.0.0 a big difference was simplifying reused concepts into “components.” This helps reduce the amount of duplication that can occur in a specification. In this example the security scheme is an API key but you can learn about others here. I’ll go into what components are a bit more in a little bit.
First, you start with defining under components object a securitySchemes object. The OpenWeatherMap API uses app_id as its API key identifier. It also specifies where the API is passed. In this case, it is a query string parameter and not a header.
components:
securitySchemes:
app_id:
type: apiKey
description: "API key to authorize requests. (If you don't have an API key, get one at https://openweathermap.org/. See https://idratherbewriting.com/learnapidoc/docapis_get_auth_keys.html for details.)"
name: appid
in: query
Once that is defined the top level security object can be defined referencing the API key to authorize requests with. Security can also be overridden at the path level if there is a need.
security:
- app_id: []
The more complex parts of the openapi specification
Paths
Paths are what do the work in an API. A path is a collection of “operations” that tie to HTTP Methods like GET/PUT/POST/DELETE. In the OpenWeatherMap API example, there is only one operation object, a GET request. While it is only one it is complicated because it can receive query string parameters that can change how it responds.
There are a few simple items that are described in the operation object. The API described in this article only uses query string parameters. Other APIs use request body or form data you can learn more about how those are described here.
- tags
- the tag grouping
- summary
- A concise summary of what the operation object does
- description
- An area to place a more thorough description of what the operation does along with supporting CommonMark formatting.
- operationId
- Unique identifier for the operation
- parameters
- If it uses query string parameters which ones are supported
- responses
- The result is returned from the API when success or error occurs along with what media type format is returned.
Ignore the ref and schema parts. For now, I will cover that in the next section.
/weather:
get:
tags:
- Current Weather Data
summary: "Call current weather data for one location"
description: "Access current weather data for any location on Earth including over 200,000 cities! Current weather is frequently updated based on global models and data from more than 40,000 weather stations."
operationId: CurrentWeatherData
parameters:
- $ref: '#/components/parameters/q'
- $ref: '#/components/parameters/id'
- $ref: '#/components/parameters/lat'
- $ref: '#/components/parameters/lon'
- $ref: '#/components/parameters/zip'
- $ref: '#/components/parameters/units'
- $ref: '#/components/parameters/lang'
- $ref: '#/components/parameters/mode'
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/200'
"404":
description: Not found response
content:
text/plain:
schema:
title: Weather not found
type: string
example: Not found
Components
Components are all the stuff that are probably going to be re-used around the API specification. This is important because you would get bored reading the same duplicated information. This minimizes that duplication. The paths example above shows reuse of parameters and response schemas. Below you can see the reference object has a value that points to where in the specification to look up the component.
- $ref: '#/components/parameters/q'
schema:
$ref: '#/components/schemas/200'
- schemas
- Think of this as reusable data models used by your application.
- responses
- Common HTTP responses your application returns.
- parameters
- Parameters that are part of how your application can be configured via query, path, headers or cookies
- examples
- Examples of response values returned from responses
- requestBodies
- The body of a request sent to an application and its content type.
- headers
- Headers the application might use such as an API key header or others.
- securitySchemes
- Types of security in use by the application.
- links
- Links point to other operations once an operation is done. It is client computed so it reduces payload duplicating this information.
- Makes my head hurt but essentially if you make a user you can know that there are links to iterate information about the user via a link.
- callbacks
- Out of band HTTP responses
- Interesting to note in 3.1 webhooks becomes a top-level feature
Below you can see the OpenWeatherMap API components section. I removed the security part since I touched on it above. You can see it uses parameters and schemas.
components:
parameters:
q:
name: q
in: query
description: "**City name**. *Example: London*. You can call by city name, or by city name and country code. The API responds with a list of results that match a searching word. For the query value, type the city name and optionally the country code divided by a comma; use ISO 3166 country codes."
schema:
type: string
id:
name: id
in: query
description: "**City ID**. *Example: `2172797`*. You can call by city ID. The API responds with the exact result. The List of city IDs can be downloaded [here](http://bulk.openweathermap.org/sample/). You can include multiple cities in this parameter — just separate them by commas. The limit of locations is 20. *Note: A single ID counts as a one API call. So, if you have city IDs, it's treated as 3 API calls.*"
schema:
type: string
lat:
name: lat
in: query
description: "**Latitude**. *Example: 35*. The latitude coordinate of the location of your interest. Must use with `lon`."
schema:
type: string
lon:
name: lon
in: query
description: "**Longitude**. *Example: 139*. Longitude coordinate of the location of your interest. Must use with `lat`."
schema:
type: string
zip:
name: zip
in: query
description: "**Zip code**. Search by zip code. *Example: 95050,us*. Please note that if the country is not specified, the search uses USA as a default."
schema:
type: string
units:
name: units
in: query
description: '**Units**. *Example: imperial*. Possible values: `standard`, `metric`, and `imperial`. When you do not use the `units` parameter, the format is `standard` by default.'
schema:
type: string
enum: [standard, metric, imperial]
default: "imperial"
lang:
name: lang
in: query
description: '**Language**. *Example: en*. You can use lang parameter to get the output in your language. We support the following languages that you can use with the corresponded lang values: Arabic - `ar`, Bulgarian - `bg`, Catalan - `ca`, Czech - `cz`, German - `de`, Greek - `el`, English - `en`, Persian (Farsi) - `fa`, Finnish - `fi`, French - `fr`, Galician - `gl`, Croatian - `hr`, Hungarian - `hu`, Italian - `it`, Japanese - `ja`, Korean - `kr`, Latvian - `la`, Lithuanian - `lt`, Macedonian - `mk`, Dutch - `nl`, Polish - `pl`, Portuguese - `pt`, Romanian - `ro`, Russian - `ru`, Swedish - `se`, Slovak - `sk`, Slovenian - `sl`, Spanish - `es`, Turkish - `tr`, Ukrainian - `ua`, Vietnamese - `vi`, Chinese Simplified - `zh_cn`, Chinese Traditional - `zh_tw`.'
schema:
type: string
enum: [ar, bg, ca, cz, de, el, en, fa, fi, fr, gl, hr, hu, it, ja, kr, la, lt, mk, nl, pl, pt, ro, ru, se, sk, sl, es, tr, ua, vi, zh_cn, zh_tw]
default: "en"
mode:
name: mode
in: query
description: "**Mode**. *Example: html*. Determines the format of the response. Possible values are `json`, `xml`, and `html`. If the mode parameter is empty, the format is `json` by default."
schema:
type: string
enum: [json, xml, html]
default: "json"
schemas:
"200":
title: Successful response
type: object
properties:
coord:
$ref: '#/components/schemas/Coord'
weather:
type: array
items:
$ref: '#/components/schemas/Weather'
description: (more info Weather condition codes)
base:
type: string
description: Internal parameter
example: cmc stations
main:
$ref: '#/components/schemas/Main'
visibility:
type: integer
description: Visibility, meter
example: 16093
wind:
$ref: '#/components/schemas/Wind'
clouds:
$ref: '#/components/schemas/Clouds'
rain:
$ref: '#/components/schemas/Rain'
snow:
$ref: '#/components/schemas/Snow'
dt:
type: integer
description: Time of data calculation, unix, UTC
format: int32
example: 1435658272
sys:
$ref: '#/components/schemas/Sys'
id:
type: integer
description: City ID
format: int32
example: 2172797
name:
type: string
example: Cairns
cod:
type: integer
description: Internal parameter
format: int32
example: 200
Coord:
title: Coord
type: object
properties:
lon:
type: number
description: City geo location, longitude
example: 145.77000000000001
lat:
type: number
description: City geo location, latitude
example: -16.920000000000002
Weather:
title: Weather
type: object
properties:
id:
type: integer
description: Weather condition id
format: int32
example: 803
main:
type: string
description: Group of weather parameters (Rain, Snow, Extreme etc.)
example: Clouds
description:
type: string
description: Weather condition within the group
example: broken clouds
icon:
type: string
description: Weather icon id
example: 04n
Main:
title: Main
type: object
properties:
temp:
type: number
description: 'Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.'
example: 293.25
pressure:
type: integer
description: Atmospheric pressure (on the sea level, if there is no sea_level or grnd_level data), hPa
format: int32
example: 1019
humidity:
type: integer
description: Humidity, %
format: int32
example: 83
temp_min:
type: number
description: 'Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.'
example: 289.81999999999999
temp_max:
type: number
description: 'Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally). Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.'
example: 295.37
sea_level:
type: number
description: Atmospheric pressure on the sea level, hPa
example: 984
grnd_level:
type: number
description: Atmospheric pressure on the ground level, hPa
example: 990
Wind:
title: Wind
type: object
properties:
speed:
type: number
description: 'Wind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour.'
example: 5.0999999999999996
deg:
type: integer
description: Wind direction, degrees (meteorological)
format: int32
example: 150
Clouds:
title: Clouds
type: object
properties:
all:
type: integer
description: Cloudiness, %
format: int32
example: 75
Rain:
title: Rain
type: object
properties:
3h:
type: integer
description: Rain volume for the last 3 hours
format: int32
example: 3
Snow:
title: Snow
type: object
properties:
3h:
type: number
description: Snow volume for the last 3 hours
example: 6
Sys:
title: Sys
type: object
properties:
type:
type: integer
description: Internal parameter
format: int32
example: 1
id:
type: integer
description: Internal parameter
format: int32
example: 8166
message:
type: number
description: Internal parameter
example: 0.0166
country:
type: string
description: Country code (GB, JP etc.)
example: AU
sunrise:
type: integer
description: Sunrise time, unix, UTC
format: int32
example: 1435610796
sunset:
type: integer
description: Sunset time, unix, UTC
format: int32
example: 1435650870
Conclusion
OpenAPI is a tool for describing RESTful HTTP APIs. It seems daunting and a lot to learn. If you can learn the high-level concepts it makes working on API-First design easier. You can also learn and apply the more complicated features as your API needs them. There is a lot of tooling out there to make this easier.