How to Create OpenAPI Specification

Mariia Berdysheva
8 min readDec 12, 2023

--

Photo by Mohammad Rahmani on Unsplash

This article will help you to create your first OpenAPI specification.

What Is OpenAPI?

OpenAPI specification is an API description format for REST APIs. This format of description is readable by both humans and machines.

You can write OpenAPI specifications in YAML or JSON.

Source: https://swagger.io/

Exploring OpenAPI Practical Usage

An analyst, developer or any other can create an OpenAPI specification to describe the API before starting development.

For developers, the OpenAPI specification is a handy tool. It helps them easily understand the API they need to implement. Moreover, developers can use the OpenAPI specification to generate the code that implements the described API.

Setting Up Your Environment

To start working on your first OpenAPI specification, you can use the official online editor, which is available at https://editor.swagger.io

Alternatively, if you prefer, you can use the locally installed Visual Studio Code Application. In this case, navigate to the extensions section and install the “Swagger Viewer” extension. This extension validates the specification. Additionally, it enables rendering the specification into the Swagger view.

Swagger Viewer Installation

Creating Your API Specification

OpenAPI version

Any OpenAPI specification starts with declaring the OpenAPI version. Currently, the latest version of OpenAPI is 3.0.3.

openapi: 3.0.3
Declaring the OpenAPI Version

To check the latest available OpenAPI version, you can visit here: https://github.com/OAI/OpenAPI-Specification/releases

Info

To make the specification informative, fill in the data related to the “info.”

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
description: An API for managing user profiles
Adding Info Details

Servers

Using “servers,” you can specify one or more URLs for API servers. Use it to include production and sandbox URLs.

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
description: An API for managing user profiles
servers:
- url: https://example.com/api/v1
description: Production server
Adding Servers Details

Tags

Add “tags” to structure the specification.

Later, when describing API endpoints (paths), use the tags to group up API methods. For example, you can use the same tag for all CRUD API methods that manage one resource.

If you don’t use tags, all API methods described in the specification will fall under the “default” header, which may not look pretty.

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
description: An API for managing user profiles
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles

Paths

One of the most important parts of the specification — “paths.” Here, you need to provide the resource path and the HTTP method.

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
description: An API for managing user profiles
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles
paths:
/profile:
post:

When designing a REST API, use different HTTP methods appropriately.

For example, use the “POST” method to create new resources, the “GET” method to retrieve resources, the “PUT” method to edit, and the “DELETE” method to remove.

These are the fundamental methods used in REST, but there are also other methods.

The supported methods in OpenAPI 3.0.3 are GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, and TRACE.

Request Body & Content

The Request Body represents the content sent to the server.

To describe the content in the OpenAPI specification, first declare the content type.

The following content types are most commonly used in REST:

  1. “application/json”
  2. “application/xml”
  3. “application/x-www-form-urlencoded”
  4. “multipart/form-data”
openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles
paths:
/profile:
post:
tags:
- profile
requestBody:
content:
application/json:

Once you have specified the content type, proceed to describe the request schema.

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles
paths:
/profile:
post:
tags:
- profile
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"

The “schema” can take either a reference or an object. I suggest using references whenever possible since we often reuse objects. In this case, we only need to describe the object once and then use references.

In REST, there’s a principle that services should be RESTful.

In RESTful design, the POST method is considered to be used to send the representation of the object that needs to be created on the server.

Once the object is successfully created on the server, the server then responds with a representation of the fully created object, which usually includes its unique identifier.

This approach ensures a predictable and consistent interaction model between clients and servers in a RESTful API.

Therefore, reuse the same object in both the request and response for the POST method. The object itself is described later in the “components.”

Responses

Each API method in the OpenAPI specification must have at least one response specified, which is typically a successful response.

The description of a response begins with its HTTP status code.

Let’s describe two responses, one with an HTTP status code 201 (Created) and the other with an HTTP status code 400 (Bad Request).

Why HTTP 201 (Created) instead of the HTTP 200 (OK)? Both work well, but following the recommendation from the standard, HTTP status code 201 is a better option — https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.3

As mentioned earlier, in the case of success (HTTP 201), the API method will return the “profile” object. In the case of an error (HTTP 400), the API method will return the “error” object.

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles
paths:
/profile:
post:
tags:
- profile
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"
responses:
"201":
description: Profile created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

Components

In the “components,” you design the API’s contract parameters.

API design is something that should be done with attention to detail. Because the API contract, once delivered in production, must remain backward compatible forever. If a mistake is made and the API contract is not designed correctly, it becomes very challenging to modify it in the future.

Begin with identifying entities/objects. In the given example, there are three entities/objects: “profile,” “address,” and “error” (for error-handling purposes).

Next, define the parameters that characterize these entities/objects.

Once the entity/object parameters are defined, it’s necessary to determine the type for each parameter (e.g., all profile parameters except for the address are strings, while the address parameter is an object type).

The tables below illustrate all three entities/objects with their parameters.

### Profile
| Parameter | Type | Format | Description |
|-----------|--------|--------|-----------------------------------|
| id | string | uuid | Unique identifier for the profile |
| firstName | string | - | First name of the user |
| lastName | string | - | Last name of the user |
| phone | string | - | Phone number of the user |
| email | string | email | Email address of the user |
| address | object | - | User's address information |

### Address
| Parameter | Type | Format | Description |
|------------|--------|--------|----------------------------------|
| street | string | - | Street address |
| city | string | - | City |
| country | string | - | Country code (ISO 3166, Alpha-2) |
| postalCode | string | - | Postal code |

### Error
| Parameter | Type | Format | Description |
|-----------|---------|--------|--------------------|
| code | integer | int32 | Numeric error code |
| message | string | - | Error message |

Finally, it is time to describe these entities/objects and their parameters in the OpenAPI specification.

The description for each parameter is more or less similar: you need to describe the type, format (optionally), description (optionally), and example (optionally).

However, OpenAPI supports many other properties. For instance, the “readOnly” (boolean) is helpful for object identifiers or similar data calculated on the server side; it indicates that you shouldn’t include the parameter in the request but should receive it in the response.

In the example below, applying “readOnly” to the identifier helps prevent the need to create two objects, namely “ProfileWithoutId” and “ProfileWithId.” Instead, you can reuse a single “Profile” object. The “id” won’t be visible in the Swagger request. Similarly, there’s the “writeOnly” property.

To learn about all the variations in the description of the parameters, please refer to the official documentation at https://swagger.io/docs/specification/data-models/

openapi: 3.0.3
info:
title: Profiles API
version: 1.0.0
description: An API for managing user profiles
servers:
- url: https://example.com/api/v1
description: Production server
tags:
- name: profile
description: Operations related to user profiles
paths:
/profile:
post:
tags:
- profile
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"
responses:
"201":
description: Profile created successfully
content:
application/json:
schema:
$ref: "#/components/schemas/Profile"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Profile:
type: object
properties:
id:
type: string
format: uuid
description: Unique identifier for the profile
readOnly: true
firstName:
type: string
description: First name of the user
example: "John"
lastName:
type: string
description: Last name of the user
example: "Doe"
phone:
type: string
description: Phone number of the user
example: "+1234567890"
email:
type: string
format: email
description: Email address of the user
example: "johndoe@example.com"
address:
$ref: "#/components/schemas/Address"
Address:
type: object
properties:
street:
type: string
description: Street address
example: "123 Wall Street"
city:
type: string
description: City
example: "New York City"
country:
type: string
description: Country code (ISO 3166, Alpha-2)
example: "US"
postalCode:
type: string
description: Postal code
example: "10005"
Error:
type: object
properties:
code:
type: integer
format: int32
description: Numeric error code
example: 400
message:
type: string
description: Error message
example: "Invalid input data provided"

Voilà, your first OpenAPI specification is now ready.

Preview Your Specification in VS Code

To preview your OpenAPI specification file in a Swagger visual format in the VS Code, follow these steps:

  1. Open Command Panel. To open the Command Panel, use the shortcut Ctrl + Shift + P (Windows/Linux) or Cmd + Shift + P (Mac).
  2. Preview Swagger. Type “Preview Swagger” in the Command Panel or choose this option from the list of suggestions. Click on — “Preview Swagger.”

Conclusion

If you’ve reached the end of this guide, I want to extend my gratitude! Your feedback on this guide or any questions are most welcome.

Thanks again!

--

--