0% found this document useful (0 votes)
5 views20 pages

Docementing Rest Api

Uploaded by

appstech234
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views20 pages

Docementing Rest Api

Uploaded by

appstech234
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Documenting REST APIs

with OpenAPI

This chapter covers


 Using JSON Schema to create validation models
for JSON documents
 Describing REST APIs with the OpenAPI
documentation standard
 Modeling the payloads for API requests and
responses
 Creating reusable schemas in OpenAPI
specifications

In this chapter, you’ll learn to document APIs using OpenAPI: the most popular
standard for describing RESTful APIs, with a rich ecosystem of tools for testing, val-
idating, and visualizing APIs. Most programming languages have libraries that sup-
port OpenAPI specifications, and in chapter 6 you’ll learn to use OpenAPI-
compatible libraries from the Python ecosystem.
OpenAPI uses JSON Schema to describe an API’s structure and models, so we
start by providing an overview of how JSON Schema works. JSON Schema is a spec-
ification for defining the structure of a JSON document, including the types and
formats of the values within the document.

90
5.1 Using JSON Schema to model data 91

After learning about JSON Schema, we study how an OpenAPI document is struc-
tured, what its properties are, and how we use it to provide informative API specifica-
tions for our API consumers. API endpoints constitute the core of the specification, so
we pay particular attention to them. We break down the process of defining the end-
points and schemas for the payloads of the API’s requests and responses, step by step.
For the examples in this chapter, we work with the API of CoffeeMesh’s orders ser-
vice. As we mentioned in chapter 1, CoffeeMesh is a fictional on-demand coffee-delivery
platform, and the orders service is the component that allows customers to place and
manage their orders. The full specification for the orders API is available under
ch05/oas.yaml in the GitHub repository for this book.

5.1 Using JSON Schema to model data


This section introduces the specification standard for JSON Schema and explains how
we leverage it to produce API specifications. OpenAPI uses an extended subset of
the JSON Schema specification for defining the structure of JSON documents and the
types and formats of its properties. It’s useful for documenting interfaces that use
JSON to represent data and to validate that the data being exchanged is correct.
The JSON Schema specification is under active development, with the latest version
being 2020-12.1

DEFINITION JSON Schema is a specification standard for defining the structure


of a JSON document and the types and formats of its properties. OpenAPI
uses JSON Schema to describe the properties of an API.

A JSON Schema specification usually defines an object with certain attributes or prop-
erties. A JSON Schema object is represented by an associative array of key-value pairs.
A JSON Schema specification usually looks like this:

Each property in a JSON Schema specification


{ comes as a key whose values are the
"status": { descriptors of the property.
"type": "string"
The minimum descriptor necessary for a
}
property is the type. In this case, we
}
specify that the status property is a string.

In this example, we define the schema of an object with one attribute named status,
whose type is string.
JSON Schema allows us to be very explicit with respect to the data types and formats
that both the server and the client should expect from a payload. This is fundamental

1
A. Wright, H. Andrews, B. Hutton, “JSON Schema: A Media Type for Describing JSON Documents” (Decem-
ber 8, 2020); https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00. You can follow the devel-
opment of JSON Schema and contribute to its improvement by participating in its repository in GitHub:
https://github.com/json-schema-org/json-schema-spec. Also see the website for the project: https://json-schema
.org/.
92 CHAPTER 5 Documenting REST APIs with OpenAPI

for the integration between the API provider and the API consumer, since it lets us
know how to parse the payloads and how to cast them into the right data types in our
runtime.
JSON Schema supports the following basic data types:
 string for character values
 number for integer and decimal values
 object for associative arrays (i.e., dictionaries in Python)
 array for collections of other data types (i.e., lists in Python)
 boolean for true or false values
 null for uninitialized data

To define an object using JSON Schema, we declare its type as object, and we list its
properties and their types. The following shows how we define an object named order,
which is one of the core models of the orders API.

Listing 5.1 Defining the schema of an object with JSON Schema

{
We can declare the
"order": {
schema as an object.
"type": "object",
"properties": {
We describe the object’s
"product": { properties under the
"type": "string" properties keyword.
},
"size": {
"type": "string"
},
"quantity": {
"type": "integer"
}
}
}
}

Since order is an object, the order attribute also has properties, defined under the
properties attribute. Each property has its own type. A JSON document that com-
plies with the specification in listing 5.1 is the following:

{
"order": {
"product": "coffee",
"size": "big",
"quantity": 1
}
}

As you can see, each of the properties described in the specification is used in this
document, and each of them has the expected type.
5.1 Using JSON Schema to model data 93

A property can also represent an array of items. In the following code, the order
object represents an array of objects. As you can see, we use the items keyword to
define the elements within the array.

Listing 5.2 Defining an array of objects with JSON Schema

{
"order": {
"type": "array", We define the elements
"items": { within the array using
"type": "object", the items keyword.
"properties": {
"product": {
"type": "string"
},
"size": {
"type": "string"
},
"quantity": {
"type": "integer"
}
}
}
}
}

In this case, the order property is an array. Array types require an additional property
in their schema, which is the items property that defines the type of each of the ele-
ments contained in the array. In this case, each of the elements in the array is an
object that represents an item in the order.
An object can have any number of nested objects. However, when too many objects
are nested, indentation grows large and makes the specification difficult to read. To
avoid this problem, JSON Schema allows us to define each object separately and to use
JSON pointers to reference them. A JSON pointer is a special syntax that allows us to
point to another object definition within the same specification.
As you can see in the following code, we can extract the definition of each item
within the order array as a model called OrderItemSchema and use a JSON pointer to
reference OrderItemSchema using the special $ref keyword.

Listing 5.3 Using JSON pointers to reference other schemas

{
"OrderItemSchema": {
"type": "object",
"properties": {
"product": {
"type": "string"
},
"size": {
"type": "string"
},
94 CHAPTER 5 Documenting REST APIs with OpenAPI

"quantity": {
"type": "integer"
}
}
},
"Order": {
"status": {
"type": "string"
},
"order": {
"type": "array",
We can specify the type
of the array’s items using
"items": {
a JSON pointer.
"$ref": '#/OrderItemSchema'
}
}
}
}

JSON pointers use the special keyword $ref and JSONPath syntax to point to another
definition within the schema. In JSONPath syntax, the root of the document is rep-
resented by the hashtag symbol (#), and the relationship of nested properties is
represented by forward slashes (/). For example, if we wanted to create a pointer
to the size property of the OrderItemSchema model, we would use the following syntax:
'#/OrderItemSchema/size'.

DEFINITION A JSON pointer is a special syntax in JSON Schema that allows us


to point to another definition within the same specification. We use the spe-
cial keyword $ref to declare a JSON pointer. To build the path to another
schema, we use JSONPath syntax. For example, to point to a schema called
OrderItemSchema, defined at the top level of the document, we use the fol-
lowing syntax: {"$ref": "#/OrderItemSchema"}.

We can refactor our specification using JSON pointers by extracting common schema
objects into reusable models, and we can reference them using JSON pointers. This
helps us avoid duplication and keep the specification clean and succinct.
In addition to being able to specify the type of a property, JSON Schema also
allows us to specify the format of the property. We can develop our own custom for-
mats or use JSON Schema’s built-in formats. For example, for a property representing
a date, we can use the date format—a built-in format supported by JSON Schema that
represents an ISO date (e.g., 2025-05-21). Here’s an example:
{
"created": {
"type": "string",
"format": "date"
}
}

In this section, we’ve worked with examples in JSON format. However, JSON Schema
documents don’t need to be written in JSON. In fact, it’s more common to write them
5.2 Anatomy of an OpenAPI specification 95

in YAML format, as it’s more readable and easier to understand. OpenAPI specifica-
tions are also commonly served in YAML format, and for the remainder of this chap-
ter, we’ll use YAML to develop the specification of the orders API.

5.2 Anatomy of an OpenAPI specification


In this section, we introduce the OpenAPI standard, and we learn to structure an API
specification. OpenAPI’s latest version is 3.1; however, this version still has little sup-
port in the current ecosystem, so we’ll document the API using OpenAPI 3.0. There’s
not much difference between the two versions, and nearly everything you learn about
OpenAPI 3.0 applies to 3.1.2
OpenAPI is a standard specification format for documenting RESTful APIs (fig-
ure 5.1). OpenAPI allows us to describe in detail every element of an API, including
its endpoints, the format of its request and response payloads, its security schemes,
and so on. OpenAPI was created in 2010 under the name of Swagger as an open
source specification format for describing RESTful web APIs. Over time, this frame-
work grew in popularity, and in 2015 the Linux Foundation and a consortium of
major companies sponsored the creation of the OpenAPI initiative, a project aimed at
improving the protocols and standards for building RESTful APIs. Today, OpenAPI is
by far the most popular specification format used to document RESTful APIs,3 and it
benefits from a rich ecosystem of tools for API visualization, testing, and validation.

openapi: 3.0

info

servers

paths

components

schemas Figure 5.1 An OpenAPI specification contains five


sections. For example, the paths section describes the
API endpoints, while the components section contains
reusable schemas referenced across the document.

2
For a detailed analysis of the differences between OpenAPI 3.0 and 3.1, check out OpenAPI’s migration from
3.0 to 3.1 guide: https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0.
3
According to the 2022 “State of the API” report by Postman (https://www.postman.com/state-of-api/api-
technologies/#api-technologies).
96 CHAPTER 5 Documenting REST APIs with OpenAPI

An OpenAPI specification contains everything that the consumer of the API needs to
know to be able to interact with the API. As you can see in figure 5.1, an OpenAPI is
structured around five sections:
 openapi—Indicates the version of OpenAPI that we used to produce the
specification.
 info—Contains general information, such as the title and version of the API.
 servers—Contains a list of URLs where the API is available. You can list more
than one URL for different environments, such as the production and staging
environments.
 paths—Describes the endpoints exposed by the API, including the expected pay-
loads, the allowed parameters, and the format of the responses. This is the most
important part of the specification, as it represents the API interface, and it’s the
section that consumers will be looking for to learn how to integrate with the API.
 components—Defines reusable elements that are referenced across the specifi-
cation, such as schemas, parameters, security schemes, request bodies, and
responses.4 A schema is a definition of the expected attributes and types in your
request and response objects. OpenAPI schemas are defined using JSON Schema
syntax.
Now that we know how to structure an OpenAPI specification, let’s move on to docu-
menting the endpoints of the orders API.

5.3 Documenting the API endpoints


In this section, we declare the endpoints of the orders API. As we mentioned in sec-
tion 5.2, the paths section of an OpenAPI specification describes the interface of your
API. It lists the URL paths exposed by the API, with the HTTP methods they imple-
ment, the types of requests they expect, and the responses they return, including the
status codes. Each path is an object whose attributes are the HTTP methods it sup-
ports. In this section, we’ll focus specifically on documenting the URL paths and the
HTTP methods. In chapter 4, we established that the orders API contains the follow-
ing endpoints:
 POST /orders—Places an order. It requires a payload with the details of the
order.
 GET /orders—Returns a list of orders. It accepts URL query parameters, which
allow us to filter the results.
 GET /orders/{order_id}—Returns the details of a specific order.
 PUT /orders/{order_id}—Updates the details of an order. Since this is a PUT
endpoint, it requires a full representation of the order.
 DELETE /orders/{order_id}—Deletes an order.

4
See https://swagger.io/docs/specification/components/ for a full list of reusable elements that can be
defined in the components section of the API specification.
5.4 Documenting URL query parameters 97

 POST /orders/{order_id}/pay—Pays for an order.


 POST /orders/{order_id}/cancel—Cancels the order.

The following shows the high-level definitions of the orders API endpoints. We
declare the URLs and the HTTP implemented by each URL, and we add an operation
ID to each endpoint so that we can reference them in other sections of the document.

Listing 5.4 High-level definition of the orders API endpoints

paths:
/orders: We declare a URL path.
get:
operationId: getOrders An HTTP method supported
post: # creates a new order by the /orders URL path
operationId: createOrder

/orders/{order_id}:
get:
operationId: getOrder
put:
operationId: updateOrder
delete:
operationId: deleteOrder

/orders/{order_id}/pay:
post:
operationId: payOrder

/orders/{order_id}/cancel:
post:
operationId: cancelOrder

Now that we have the endpoints, we need to fill in the details. For the GET /orders
endpoint, we need to describe the parameters that the endpoint accepts, and for
the POST and PUT endpoints, we need to describe the request payloads. We also
need to describe the responses for each endpoint. In the following sections, we’ll
learn to build specifications for different elements of the API, starting with the
URL query parameters.

5.4 Documenting URL query parameters


As we learned in chapter 4, URL query parameters allow us to filter and sort the
results of a GET endpoint. In this section, we learn to define URL query parameters
using OpenAPI. The GET /orders endpoint allows us to filter orders using the follow-
ing parameters:
 cancelled—Whether the order was cancelled. This value will be a Boolean.
 limit—Specifies the maximum number of orders that should be returned to
the user. The value for this parameter will be a number.
98 CHAPTER 5 Documenting REST APIs with OpenAPI

Both cancelled and limit can be combined within the same request to filter the results:

GET /orders?cancelled=true&limit=5

This request asks the server for a list of five orders that have been cancelled. Listing
5.5 shows the specification for the GET /orders endpoint’s query parameters. The
definition of a parameter requires a name, which is the value we use to refer to it in the
actual URL. We also specify what type of parameter it is. OpenAPI 3.1 distinguishes
four types of parameters: path parameters, query parameters, header parameters, and
cookie parameters. Header parameters are parameters that go in an HTTP header
field, while cookie parameters go into a cookie payload. Path parameters are part of
the URL path and are typically used to identify a resource. For example, in /orders/
{order_id}, order_id is a path parameter that identifies a specific order. Query
parameters are optional parameters that allow us to filter and sort the results of an
endpoint. We define the parameter’s type using the schema keyword (Boolean in the
case of cancelled, and a number in the case of limit), and, when relevant, we specify
the format of the parameter as well.5

Listing 5.5 Specification for the GET /orders endpoint’s query parameters

paths: We describe URL query parameters


/orders: under the parameters property.
get:
parameters: The parameter’s
- name: cancelled
name
in: query
required: false
We use the in descriptor to specify that
the parameter goes in the URL path.
schema:
type: boolean We specify whether the
- name: limit parameter is required.
in: query
required: false We specify the parameter’s
schema: type under schema.
type: integer

Now that we know how to describe URL query parameters, in the next section we’ll
tackle something more complex: documenting request payloads.

5.5 Documenting request payloads


In chapter 4, we learned that a request represents the data sent by a client to the
server through a POST or a PUT request. In this section, we learn to document the
request payloads of the orders API endpoints. Let’s start with the POST /orders
method. In section 5.1, we established that the payload for the POST /orders end-
point looks like this:

5
To learn more about the date types and formats available in OpenAPI 3.1 see http://spec.openapis.org/
oas/v3.1.0#data-types.
5.5 Documenting request payloads 99

{
"order": [
{
"product": "cappuccino",
"size": "big",
"quantity": 1
}
]
}

This payload contains an attribute order, which represents an array of items. Each
item is defined by the following three attributes and constraints:
 product—The type of product the user is ordering.
 size—The size of the product. It can be one of the three following choices:
small, medium, and big.
 quantity—The amount of the product. It can be any integer number equal to
or greater than 1.
Listing 5.6 shows how we define the schema for this payload. We define request pay-
loads under the content property of the method’s requestBody property. We can
specify payloads in different formats. In this case, we allow data only in JSON format,
which has a media type definition of application/json. The schema for our payload
is an object with one property: order, whose type is array. The items in the array are
objects with three properties: the product property, with type string; the size prop-
erty, with type string; and the quantity property, with type integer. In addition, we
define an enumeration for the size property, which constrains the accepted values to
small, medium, and big. Finally, we also provide a default value of 1 for the quantity
property, since it’s the only nonrequired field in the payload. Whenever a user sends a
request containing an item without the quantity property, we assume that they want
to order only one unit of that item.

Listing 5.6 Specification for the POST /orders endpoint

paths:
/orders:
post: We describe
operationId: createOrder request payloads
requestBody: under requestBody.
required: true
We specify
content:
whether the We specify the
payload is application/json:
payload’s content
required. schema:
type.
type: object
properties:
We define the order:
payload’s type: array
schema.
items:
type: object
properties:
100 CHAPTER 5 Documenting REST APIs with OpenAPI

product:
type: string
We can constrain the size:
property’s values using type: string
an enumeration. enum:
- small
- medium
- big
quantity:
type: integer
We specify a
required: false
default value.
default: 1
required:
- product
- size

Embedding payload schemas within the endpoints’ definitions, as in listing 5.6, can
make our specification more difficult to read and understand. In the next section, we
learn to refactor payload schemas for reusability and for readability.

5.6 Refactoring schema definitions to avoid repetition


In this section, we learn strategies for refactoring schemas to keep the API specification
clean and readable. The definition of the POST /orders endpoint in listing 5.6 is long
and contains several layers of indentation. As a result, it’s difficult to read, and that means
in the future it’ll become difficult to extend and to maintain. We can do better by moving
the payload’s schema to a different section of the API specification: the components sec-
tion. As we explained in section 5.2, the components section is used to declare schemas
that are referenced across the specification. Every schema is an object where the key is
the name of the schema, and the values are the properties that describe it.

Listing 5.7 Specification for the POST /orders endpoint using a JSON pointer

paths:
/orders:
post:
operationId: createOrder
requestBody:
required: true We use a JSON pointer to
content: reference a schema defined
application/json: somewhere else in the document.
schema:
$ref: '#/components/schemas/CreateOrderSchema'

components:
schemas:
Schema CreateOrderSchema:
definitions Every schema is an
type: object object, where the key is
go under properties:
components. the name and the values
order: are the properties that
type: array describe it.
items:
5.6 Refactoring schema definitions to avoid repetition 101

type: object
properties:
product:
type: string
size:
type: string
enum:
- small
- medium
- big
quantity:
type: integer
required: false
default: 1
required:
- product
- size

Moving the schema for the POST /orders request payload under the components sec-
tion of the API makes the document more readable. It allows us to keep the paths
section of the document clean and focused on the higher-level details of the endpoint.
We simply need to refer to the CreateOrderSchema schema using a JSON pointer:

#/components/schemas/CreateOrderSchema

The specification is looking good, but it can get better. CreateOrderSchema is a tad
long, and it contains several layers of nested definitions. If CreateOrderSchema grows
in complexity, over time it’ll become difficult to read and maintain. We can make it
more readable by refactoring the definition of the order item in the array in the fol-
lowing code. This strategy allows us to reuse the schema for the order’s item in other
parts of the API.

Listing 5.8 Schema definitions for OrderItemSchema and Order

components:
schemas:
OrderItemSchema:
type: object
We introduce the
OrderItemSchema.
properties:
product:
type: string
size:
type: string
enum:
- small
- medium
- big
quantity:
type: integer
default: 1
CreateOrderSchema:
type: object
102 CHAPTER 5 Documenting REST APIs with OpenAPI

properties:
order:
type: array We use a JSON
items: pointer to point to
$ref: '#/OrderItemSchema' OrderItemSchema.

Our schemas are looking good! The CreateOrderSchema schema can be used to create
an order or to update it, so we can reuse it in the PUT /orders/{order_id} endpoint,
as you can see in listing 5.9. As we learned in chapter 4, the /orders/{order_id} URL
path represents a singleton resource, and therefore the URL contains a path parame-
ter, which is the order’s ID. In OpenAPI, path parameters are represented between
curly braces. We specify that the order_id parameter is a string with a UUID format (a
long, random string often used as an ID).6 We define the URL path parameter directly
under the URL path to make sure it applies to all HTTP methods.

Listing 5.9 Specification for the PUT /orders/{order_id} endpoint

paths:
/orders: We declare
get: the order’s
... resource URL. We define the URL
path parameter.
/orders/{order_id}: The order_id parameter
parameters: is part of the URL path.
- in: path
name: order_id
required: true The name of the parameter
We specify the schema:
parameter’s type: string The order_id
format (UUID). format: uuid parameter is required.
put:
operationId: updateOrder We define the HTTP
requestBody: method PUT for the
We document current URL path.
the request required: true
body of the content:
PUT endpoint. application/json:
schema:
$ref: '#/components/schemas/CreateOrderSchema'

Now that we understand how to define the schemas for our request payloads, let’s
turn our attention to the responses.

5.7 Documenting API responses


In this section, we learn to document API responses. We start by defining the payload
for the GET /orders/{order_id} endpoint. The response of the GET /orders/
{order_id} endpoint looks like this:

6
P. Leach, M. Mealling, and R. Salz, “A Universally Unique Identifier (UUID) URN Namespace,” RFC 4112
(https://datatracker.ietf.org/doc/html/rfc4122).
5.7 Documenting API responses 103

{
"id": "924721eb-a1a1-4f13-b384-37e89c0e0875",
"status": "progress",
"created": "2022-05-01",
"order": [
{
"product": "cappuccino",
"size": "small",
"quantity": 1
},
{
"product": "croissant",
"size": "medium",
"quantity": 2
}
]
}

This payload shows the products ordered by the user, when the order was placed, and
the status of the order. This payload is similar to the request payload we defined in sec-
tion 5.6 for the POST and PUT endpoints, so we can reuse our previous schemas.

Listing 5.10 Definition of the GetOrderSchema schema

components:
schemas:
OrderItemSchema:
... We define the
GetOrderSchema
GetOrderSchema: schema.
type: object
properties:
We constrain the values of
status:
the status property with
type: string
an enumeration.
enum:
- created
- paid
- progress
- cancelled
- dispatched
- delivered
created:
A string with
type: string
date-time format
format: date-time
order:
We reference the
OrderItemSchema
type: array
schema using a
items:
JSON pointer.
$ref: '#/components/schemas/OrderItemSchema'

In listing 5.10, we use a JSON pointer to point to GetOrderSchema. An alternative way


to reuse the existing schemas is to use inheritance. In OpenAPI, we can inherit and
extend a schema using a strategy called model composition, which allows us to combine
the properties of different schemas in a single object definition. The special keyword
104 CHAPTER 5 Documenting REST APIs with OpenAPI

allOf is used in these cases to indicate that the object requires all the properties in
the listed schemas.

DEFINITION Model composition is a strategy in JSON Schema that allows us to


combine the properties of different schemas into a single object. It is useful
when a schema contains properties that have already been defined elsewhere,
and therefore allows us to avoid repetition.

The following code shows an alternative definition of GetOrderSchema using the


allOf keyword. In this case, GetOrderSchema is the composition of two other schemas:
CreateOrderSchema and an anonymous schema with two keys— status and created.

Listing 5.11 Alternative implementation of GetOrderSchema using the allOf keyword

components:
schemas:
OrderItemSchema:
... We use the allOf keyword
to inherit properties from
GetOrderSchema: We use a JSON
other schemas.
allOf: pointer to reference
- $ref: '#/components/schemas/CreateOrderSchema' another schema.
- type: object
properties: We define a new object to
status: include properties that are
type: string specific to GetOrderSchema.
enum:
- created
- paid
- progress
- cancelled
- dispatched
- delivered
created:
type: string
format: date-time

Model composition results in a cleaner and more succinct specification, but it only
works if the schemas are strictly compatible. If we decide to extend CreateOrderSchema
with new properties, then this schema may no longer be transferable to the GetOrder-
Schema model. In that sense, it’s sometimes better to look for common elements among
different schemas and refactor their definitions into standalone schemas.
Now that we have the schema for the GET /orders/{order_id} endpoint’s response
payload, we can complete the endpoint’s specification. We define the endpoint’s
responses as objects in which the key is the response’s status code, such as 200. We also
describe the response’s content type and its schema, GetOrderSchema.

Listing 5.12 Specification for the GET /orders/{order_id} endpoint

paths:
/orders:
5.8 Creating generic responses 105

get:
...

/orders/{order_id}:
parameters:
- in: path
name: order_id
required: true
schema:
type: string
format: uuid

put: We define the GET


... endpoint of the
/orders/{order_id} We provide a
URL path. summary description
get:
We define summary: Returns the details of a specific order of this endpoint.
this endpoint’s operationId: getOrder
responses. responses:
A brief description
'200':
Each response is of the response
description: OK
an object where We use a
the key is the content:
application/json:
We describe the content JSON pointer
status code. types of the response. to reference
schema:
$ref: '#/components/schemas/GetOrderSchema' GetOrderSchema.

As you can see, we define response schemas within the responses section of the end-
point. In this case, we only provide the specification for the 200 (OK) successful
response, but we can also document other status codes, such as error responses. The next
section explains how we create generic responses we can reuse across our endpoints.

5.8 Creating generic responses


In this section, we learn to add error responses to our API endpoints. As we men-
tioned in chapter 4, error responses are more generic, so we can use the components
section of the API specification to provide generic definitions of those responses, and
then reuse them in our endpoints.
We define generic responses within the responses header of the API’s components
section. The following shows a generic definition for a 404 response named NotFound.
As with any other response, we also document the content’s payload, which in this
case is defined by the Error schema.

Listing 5.13 Generic 404 status code response definition

We name the
response. Generic responses go
components:
under responses in the
components section.
responses:
NotFound: We describe
description: The specified resource was not found. the response.
106 CHAPTER 5 Documenting REST APIs with OpenAPI

content:
We define
application/json:
the response’s
content. schema:
$ref: '#/components/schemas/Error'
We reference the
Error schema.
schemas:
OrderItemSchema:
...

Error:
We define the
type: object
schema for the
properties: Error payload.
detail:
type: string
required:
- detail

This specification for the 404 response can be reused in the specification of all our
endpoints under the /orders/{order_id} URL path, since all of those endpoints are
specifically designed to target a specific resource.
NOTE You may be wondering, if certain responses are common to all the end-
points of a URL path, why can’t we define the responses directly under the
URL path and avoid repetition? The answer is this isn’t possible as of now. The
responses keyword is not allowed directly under a URL path, so we must docu-
ment all the responses for every endpoint individually. There’s a request in the
OpenAPI GitHub repository to allow including common responses directly
under the URL path, but it hasn’t been implemented (http://mng.bz/097p).
We can use the generic 404 response from listing 5.13 under the GET /orders/
{order_id} endpoint.

Listing 5.14 Using the 404 response schema under GET /orders/{order_id}

paths:
...

/orders/{order_id}:
parameters:
- in: path
name: order_id
required: true
schema:
type: string
"format": uuid
get:
summary: Returns the details of a specific order
operationId: getOrder
responses:
'200':
description: OK
content:
application/json:
5.9 Defining the authentication scheme of the API 107

schema:
We define $ref: '#/components/schemas/GetOrderSchema' We reference the
a 404 '404': NotFound response
response. $ref: '#/components/responses/NotFound' using a JSON pointer.

The orders API specification in the GitHub repository for this book also contains a
generic definition for 422 responses and an expanded definition of the Error compo-
nent that accounts for the different error payloads we get from FastAPI.
We’re nearly done. The only remaining endpoint is GET /orders, which returns a
list of orders. The endpoint’s payload reuses GetOrderSchema to define the items in
the orders array.

Listing 5.15 Specification for the GET /orders endpoint

paths:
/orders: We define the new
get: GET method of the
operationId: getOrders /orders URL path.
responses:
'200':
description: A JSON array of orders
content:
application/json:
schema:
type: object
properties:
orders is
orders:
an array.
type: array
items:
$ref: '#/components/schemas/GetOrderSchema'
required:
- order Each item in the array is
defined by GetOrderSchema.
post:
...

/orders/{order_id}:
parameters:
...

Our API’s endpoints are now fully documented! You can use many more elements
within the definitions of your endpoints, such as tags and externalDocs. These attri-
butes are not strictly necessary, but they can help to provide more structure to your
API or make it easier to group the endpoints. For example, you can use tags to create
groups of endpoints that logically belong together or share common features.
Before we finish this chapter, there’s one more topic we need to address: docu-
menting the authentication scheme of our API. That’s the topic of the next section!

5.9 Defining the authentication scheme of the API


If our API is protected, the API specification must describe how users need to authen-
ticate and authorize their requests. This section explains how we document our API’s
108 CHAPTER 5 Documenting REST APIs with OpenAPI

security schemes. The security definitions of the API go within the components section
of the specification, under the securitySchemes header.
With OpenAPI, we can describe different security schemes, such as HTTP-based
authentication, key-based authentication, Open Authorization 2 (OAuth2), and OpenID
Connect.7 In chapter 11, we’ll implement authentication and authorization using the
OpenID Connect and OAuth2 protocols, so let’s go ahead and add definitions for
these schemes. Listing 5.16 shows the changes we need to make to our API specifica-
tion to document the security schemes.
We describe three security schemes: one for OpenID Connect, another one for
OAuth2, and another for bearer authorization. We’ll use OpenID Connect to authorize
user access through a frontend application, and for direct API integrations, we’ll offer
OAuth’s client credentials flow. We’ll explain how each protocol and each authorization
flow works in detail in chapter 11. For OpenID Connect, we must provide a configura-
tion URL that describes how our backend authentication works under the
openIdConnectUrl property. For OAuth2, we must describe the authorization flows
available, together with a URL that clients must use to obtain their authorization tokens
and the available scopes. The bearer authorization tells users that they must include a
JSON Web Token (JWT) in the Authorization header to authorize their requests.

Listing 5.16 Documenting an API’s security scheme

components:
responses:
...
The security schemes under We provide a name for
the securitySchemes header the security scheme (it
schemas:
of the API’s components can be any name).
... section
securitySchemes: The type of
openId: security scheme
The name type: openIdConnect
of another openIdConnectUrl: https://coffeemesh-dev.eu.auth0.com/.well-
security ➥ known/openid-configuration The URL that describes the
scheme oauth2:
A description OpenID Connect configuration
type: oauth2 in our backend
The type of of the client
flows:
the security credentials flow
scheme clientCredentials:
tokenUrl: https://coffeemesh-dev.eu.auth0.com/oauth/token
The authorization scopes: {}
bearerAuth: The available scopes The URL where
flows available
under this type: http when requesting an users can request
security scheme scheme: bearer authorization token authorization tokens
bearerFormat: JWT
... The bearer token has
a JSON Web Token
(JWT) format.

7
For a complete reference of all the security schemas available in OpenAPI, see https://swagger.io/docs/
specification/authentication/.
Summary 109

security:
- oauth2:
- getOrders
- createOrder
- getOrder
- updateOrder
- deleteOrder
- payOrder
- cancelOrder
- bearerAuth:
- getOrders
- createOrder
- getOrder
- updateOrder
- deleteOrder
- payOrder
- cancelOrder

This concludes our journey through documenting REST APIs with OpenAPI. And
what a ride! You’ve learned how to use JSON Schema; how OpenAPI works; how to
structure an API specification; how to break down the process of documenting your
API into small, progressive steps; and how to produce a full API specification. The
next time you work on an API, you’ll be well positioned to document its design using
these standard technologies.

Summary
 JSON Schema is a specification for defining the types and formats of the prop-
erties of a JSON document. JSON Schema is useful for defining data validation
models in a language-agnostic manner.
 OpenAPI is a standard documentation format for describing REST APIs and
uses JSON Schema to describe the properties of the API. By using OpenAPI,
you’re able to leverage the whole ecosystem of tools and frameworks built around
the standard, which makes it easier to build API integrations.
 A JSON pointer allows you to reference a schema using the $ref keyword. Using
JSON pointers, we can create reusable schema definitions that can be used in dif-
ferent parts of an API specification, keeping the API specification clean and easy
to understand.
 An OpenAPI specification contains the following sections:
– openapi—Specifies the version of OpenAPI used to document the API
– info—Contains information about the API, such as its title and version
– servers—Documents the URLs under which the API is available
– paths—Describes the endpoints exposed by the API, including the schemas
for the API requests and responses and any relevant URL path or query
parameters
– components—Describes reusable components of the API, such as payload
schemas, generic responses, and authentication schemes

You might also like