OpenAPI: How to Design API Specifications

OpenAPI is a JSON or YAML based specification format. It helps you in designing a detailed specification of your API: endpoints, payloads to use, expected HTML status code. Such a specification gives you the opportunity of using various OpenAPI tools: visualizing your API, performing automatic requests, and even code validation or code generation.

This article gives an introduction to writing an OpenAPI specification. The running example for this endeavor will be the internal API of my lighthouse SAAS. We will see how general metadata, API endpoints and the structure of response/request data can be specified.

This article originally appeared at my blog.

OpenAPI Building Blocks

  • Metadata: This section encodes information about the API specification itself, like version, title, author or further links
  • Servers: A list of servers that offer the API you are describing, it is used by some tools to generate curl statements that you can use live to query the API
  • Endpoints: The concrete endpoints of your API, described as complex objects with this information: Path, HTTP methods, parameters (required/optional), and responses with status code, content-type and response
  • Schemas/Definitions: All structured data objects, either used in requests or returned in the response, can be defined in this section and then referenced from other parts within the specification

These are the building blocks. Some nuances come with different versions of OpenAPI: It was formerly called Swagger, and many older projects still use this version. This article covers exclusively the OpenAPI format.

Example: Lighthouse API

The scanner provides two main endpoints: Starting a scan with /scan, where a url query parameter is passed, and to get the status of a /api/jobs by passing a uuid query parameter. We will see how to specify these two endpoints, their parameters, their response codes and return values/objects.

OpenAPI Declaration for Lighthouse

Info and Listing Servers

openapi: 3.0.0
info:
version: 1.0
title: Lighthouse API
servers:
- url: https://lighthouse.admantium.com/

You can also add a list of servers that are hosting your API. This information will be used by some tools to generate code that calls your API directly, or for examples that use the curl command to call the endpoint directly.

servers:
- url: https://lighthouse.admantium.com/

Scanner Endpoint

  • You define the endpoints in a section called paths
  • Each path has at least one HTTP method: get, post, put, option, query, delete
  • Each method has a description, parameters and responses
  • responses have at least one status code that they return.

The first version of the scanner endpoint description looks like this:

paths:
/scan:
get:
description: Initiate a webpage scan
parameters:
...
responses:
200:
...
400:
...
429:
...

Then, we detail the parameters. First, you define the parameters name. Then, you define where the parameter is in-cluded: In the query, or in the body. The schema defines the type.

paths:
/scan:
get:
description: Initiate a webpage scan
parameters:
- name: url
in: query
schema:
type: string

And finally, we give more details about the response. Following the status code, you add the description and its content. The content first lists the mime-type, which can be familiar values such as application/json and text/html. If it is JSON, you can provide further details in the form of a complete schema definition - we will look to it in a later place.

paths:
/scan:
get:
# ...
responses:
200:
description: Webpage scan is accepted and will be processed
content:
application/json:
schema:
$ref: '#/components/schemas/ScanResponse'
400:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/DefaultError'
429:
description: Scan requests can not be accepted, all workers are occupied
content:
application/json:
schema:
$ref: '#/components/schemas/ScanResponse'

Job Endpoint

/api/jobs:
get:
description: Get the job status for the provided `uuid`
parameters:
- name: uuid
in: query
schema:
type: string
responses:

The responses are 200 for a completed, 202 for an ongoing job, 409 if the job has an error, and 400 if the request is faulty. Whatever the status code, the mime-type is always application/json. The concrete response payload is, similar to before, detailed as a separate object.

responses:
200:
description: 'JOB status: finished'
content:
application/json:
schema:
$ref: '#/components/schemas/JobResponse'
202:
description: 'JOB status: started'
content:
application/json:
schema:
$ref: '#/components/schemas/JobResponse'
400:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/DefaultError'
409:
description: 'JOB status: error'
content:
application/json:
schema:
$ref: '#/components/schemas/JobResponse'

Defining Request or Response JSON Payload Data Structures

responses:
200:
description: Webpage scan is accepted and will be processed
content:
application/json:
schema:
$ref: '#/components/schemas/ScanResponse'

Lets see the details of this schema definition. It starts with the name ScanResponse, and its type is object. This object has properties: a string msg, a integer code, and a string uuid. You can even define regular-expressions to further specify the structure of individual attributes.

components:
schemas:
ScanResponse:
type: object
properties:
msg:
type: string
code:
type: integer
uuid:
type: string

The definition of the JobResponse datatype is more complex because it has an inherent structure: Its properties are msg, code and job, which in itself is an object with uuid, domain, status and record. This complexity is simplified in an OpenAPI specification because you just embed the job declaration. Here is the result:

JobResponse:
type: object
properties:
msg:
type: string
code:
type: integer
job:
type: object
properties:
uuid:
type: string
domain:
type: string
status:
type: string
record:
type: boolean

Complete Example

Summary

IT Project Manager & Developer