OpenAPI: How to Design API Specifications
Tags: api
Published: 2021-01-04 06:24:54

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](https://lighthouse.admantium.com). 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](https://blog.admantium.com/blog/tech06_openapi/)_.

## OpenAPI Building Blocks

 An OpenAPI specification consists of the following elements:

- 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

Lighthouse is a service to scan webpages and see how good they score on terms of SEO, performance and best practices. You can use the scanner here: **[https://lighthouse.admantium.com/](https://lighthouse.admantium.com/)**.

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

We will use the current OpenAPI version 3.0 and the YAML data format for defining the API.

### Info and Listing Servers

At the beginning of your document, you are providing the essential metadata about the specification, things like which API version you are using, version, title etc.

```yaml
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.

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

### Scanner Endpoint

The scanner endpoint accepts HTTP `GET` requests and requires a `url` parameter. It responds with three status codes: `200`, `400` and `429`. Lets build this step-by-step:

- 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:

```yaml
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`.

```yaml
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.

```yaml
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

Another endpoint of the application provides the capabilities to check for the status of a scanning job. This endpoint is available at `/api/jobs`, and it needs to be called with an `uuid` query parameter.

```yaml
/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.

```yaml
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

Lets go back to the `/scan` endpoint and its return result. Below the mime-type definition of `application/json`, we see a `$ref` definition, which is a pointer to an existing specification. In this particular example, the pointer reads as `#/components/schemas/ScanResponse`, which literally means "in this file, at `components/schemas`, use the `ScanResponse` datatype".

```yaml
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.

```yaml
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:

```yaml
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

You can see the complete example of the lighthouse API spec [here](https://blog.admantium.com/lighthouse_openapi_spec/).

## Summary

With OpenAPI, you can specify your API in its details: endpoints, parameter, request/response objects. In a concise, declarative language you define these elements. The specification model can then be used by various tools: documentation viewer, validators, and even code generators. In the next article, you can read more about this tool chain.