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 &mdash; 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.