|
| 1 | +--- |
| 2 | +title: Pagination |
| 3 | +description: Learn how to implement pagination in OpenAPI specifications, including query parameters, headers, and response formats. |
| 4 | +--- |
| 5 | + |
| 6 | +# Pagination in OpenAPI |
| 7 | + |
| 8 | +Describing a collection of resources in an API may be simple at first, but |
| 9 | +certain features like pagination can make it more complex. Pagination is a |
| 10 | +common requirement for APIs that return large sets of data, which allows for a |
| 11 | +subsection of a collection to be returned (maybe only 20-100 resources) to avoid |
| 12 | +overwhelming the server, the client, and all the network components in between. |
| 13 | + |
| 14 | +This is usually implemented as a query parameter, such as `?page=1` or |
| 15 | +`?cursor=abc123`, so that a client can request a specific page of results. The |
| 16 | +API can then return a subset of the data, and depending on the specific |
| 17 | +pagination strategy used there could also be metadata about the total number of |
| 18 | +items and the total number of pages. That metadata allows a client to display |
| 19 | +pagination controls in the interface such as "Page 1", "Page 2", or simply |
| 20 | +"Next" and "Previous" buttons. |
| 21 | + |
| 22 | +You can learn more about various approaches to pagination in the API design guide |
| 23 | +[here](https://www.moesif.com/blog/api-design-guide/pagination/). |
| 24 | + |
| 25 | +This guide will show you how to implement pagination in OpenAPI, regardless of |
| 26 | +which strategy the API has chosen. |
| 27 | + |
| 28 | +## Pagination with query parameters |
| 29 | + |
| 30 | +Query parameters are a common way to implement pagination in APIs. The most |
| 31 | +common query parameters for pagination are `page` and `limit`, which specify the |
| 32 | +page number and the number of items per page, respectively. |
| 33 | +This is a common approach for paginating through large sets of data, and is |
| 34 | +often used in REST APIs. |
| 35 | + |
| 36 | +```yaml |
| 37 | +paths: |
| 38 | + /stations: |
| 39 | + get: |
| 40 | + summary: Get a list of train stations |
| 41 | + description: Returns a paginated and searchable list of all train stations. |
| 42 | + operationId: get-stations |
| 43 | + parameters: |
| 44 | + - name: page |
| 45 | + in: query |
| 46 | + description: The page number to return |
| 47 | + required: false |
| 48 | + schema: |
| 49 | + type: integer |
| 50 | + minimum: 1 |
| 51 | + default: 1 |
| 52 | + example: 1 |
| 53 | + - name: limit |
| 54 | + in: query |
| 55 | + description: The number of items to return per page |
| 56 | + required: false |
| 57 | + schema: |
| 58 | + type: integer |
| 59 | + minimum: 1 |
| 60 | + maximum: 100 |
| 61 | + default: 10 |
| 62 | + example: 10 |
| 63 | +``` |
| 64 | +
|
| 65 | +Adding the `page` and `limit` query parameters advertises to the API consumers |
| 66 | +that the API supports pagination. Pagination is mentioned in the description |
| 67 | +too, increasing the chance of it being noticed by any API client developers. |
| 68 | + |
| 69 | +The `page` parameter specifies the page number the API should return, and the |
| 70 | +optional `limit` parameter specifies the number of items to return for that |
| 71 | +page. This is helpful for mobile apps that want to return a grid of data to the |
| 72 | +user. For example a 3x3 grid of items, which would require 9 items to be |
| 73 | +returned, instead of the default 10 giving the user a blank space in the grid. |
| 74 | + |
| 75 | +## Describing pagination metadata |
| 76 | + |
| 77 | +When implementing pagination, a common practice is to include metadata in the |
| 78 | +response to provide information about the total number of items, the current |
| 79 | +page, and the total number of pages. This metadata can be included in the |
| 80 | +response body, or sometimes is done with custom HTTP headers (which is frowned |
| 81 | +upon but done anyway). |
| 82 | + |
| 83 | +Using a `meta` object in an array would not work, so APIs often wrap the |
| 84 | +collection with an "envelope" which might be something like `data`. |
| 85 | + |
| 86 | +```json |
| 87 | +{ |
| 88 | + "data": [ |
| 89 | + ... |
| 90 | + ], |
| 91 | + "meta": { |
| 92 | + "page": 2, |
| 93 | + "size": 10, |
| 94 | + "total_pages": 100 |
| 95 | + } |
| 96 | +``` |
| 97 | + |
| 98 | +If the API is doing this, the response can be described with the following OpenAPI: |
| 99 | + |
| 100 | +```yaml |
| 101 | +responses: |
| 102 | + '200': |
| 103 | + description: A paginated list of train stations. |
| 104 | + content: |
| 105 | + application/json: |
| 106 | + schema: |
| 107 | + type: object |
| 108 | + properties: |
| 109 | + data: |
| 110 | + type: array |
| 111 | + items: |
| 112 | + $ref: '#/components/schemas/Station' |
| 113 | + meta: |
| 114 | + type: object |
| 115 | + properties: |
| 116 | + page: |
| 117 | + type: integer |
| 118 | + example: 2 |
| 119 | + size: |
| 120 | + type: integer |
| 121 | + example: 10 |
| 122 | + total_pages: |
| 123 | + type: integer |
| 124 | + example: 100 |
| 125 | +``` |
| 126 | + |
| 127 | +This meta object can be defined in `components` and referenced to avoid repeating it |
| 128 | +in every endpoint. |
| 129 | + |
| 130 | +Learn more about components [here](/openapi/components). |
| 131 | + |
| 132 | +## Describing pagination with response links |
| 133 | + |
| 134 | +Adding a query parameter to the API is a good start, but asking API clients to |
| 135 | +construct URLs from little bits of data is always confusing and a recipe for |
| 136 | +disaster. REST APIs offer the ability to send links which can be used to |
| 137 | +crawl the API following links like a browser would. |
| 138 | + |
| 139 | +If the API supports pagination links they |
| 140 | +need to be described so clients can use them. The most common way to do this is to |
| 141 | +include a `links` object in the response. |
| 142 | + |
| 143 | +```json |
| 144 | +{ |
| 145 | + "data": [ |
| 146 | + ... |
| 147 | + ], |
| 148 | + "links": { |
| 149 | + "self": "https://api.example.com/stations?page=2", |
| 150 | + "next": "https://api.example.com/stations?page=3", |
| 151 | + "prev": "https://api.example.com/stations?page=1" |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +That links object can be described in OpenAPI like using the following approach: |
| 157 | + |
| 158 | +```yaml |
| 159 | +responses: |
| 160 | + '200': |
| 161 | + description: OK |
| 162 | + content: |
| 163 | + application/json: |
| 164 | + schema: |
| 165 | + allOf: |
| 166 | + - $ref: '#/components/schemas/Wrapper-Collection' |
| 167 | + - properties: |
| 168 | + data: |
| 169 | + type: array |
| 170 | + items: |
| 171 | + $ref: '#/components/schemas/Station' |
| 172 | + - properties: |
| 173 | + links: |
| 174 | + allOf: |
| 175 | + - $ref: '#/components/schemas/Links-Self' |
| 176 | + - $ref: '#/components/schemas/Links-Pagination' |
| 177 | +
|
| 178 | +components: |
| 179 | + Station: |
| 180 | + description: A train station. |
| 181 | + type: object |
| 182 | + properties: |
| 183 | + id: |
| 184 | + type: string |
| 185 | + format: uuid |
| 186 | + description: Unique identifier for the station. |
| 187 | + examples: |
| 188 | + - efdbb9d1-02c2-4bc3-afb7-6788d8782b1e |
| 189 | + - b2e783e1-c824-4d63-b37a-d8d698862f1d |
| 190 | + # ... snip ... |
| 191 | + Links-Self: |
| 192 | + description: The link to the current resource. |
| 193 | + type: object |
| 194 | + properties: |
| 195 | + self: |
| 196 | + type: string |
| 197 | + format: uri |
| 198 | + Links-Pagination: |
| 199 | + description: Links to the next and previous pages of a paginated response. |
| 200 | + type: object |
| 201 | + properties: |
| 202 | + next: |
| 203 | + type: string |
| 204 | + format: uri |
| 205 | + prev: |
| 206 | + type: string |
| 207 | + format: uri |
| 208 | + Wrapper-Collection: |
| 209 | + type: object |
| 210 | + properties: |
| 211 | + data: |
| 212 | + description: The wrapper for a collection is an array of objects. |
| 213 | + type: array |
| 214 | + items: |
| 215 | + type: object |
| 216 | + links: |
| 217 | + description: A set of hypermedia links which serve as controls for the client. |
| 218 | + type: object |
| 219 | + readOnly: true |
| 220 | +``` |
| 221 | + |
| 222 | +In this example, the `links` object contains three links: `self`, `next`, and |
| 223 | +`prev`. The `self` link points to the current page of results, while the `next` |
| 224 | +and `prev` links point to the next and previous pages of results, respectively. |
| 225 | +The `links` object is described in the OpenAPI specification using the `links` |
| 226 | +object, which is a common way to describe hypermedia links in OpenAPI. |
| 227 | + |
| 228 | +## Describing pagination with HTTP headers |
| 229 | + |
| 230 | +Some APIs use custom HTTP headers to provide pagination information instead of trying to wedge the information into the response body and using `data` and `meta`. This has the benefit of keeping the JSON clean and tidy, but can be confusing as there is no standard for pagination headers. The `Link` header is a standard HTTP header that can be used to provide links for general HATEOAS purposes but also for pagination specifically, but it does not have anywhere to pass pagination metadata about numbers of pages. |
| 231 | + |
| 232 | +For example, an API might use something like `X-Total-Count` header to indicate the total number of items in |
| 233 | +the collection, and the `X-Page` and `X-Per-Page` headers to indicate the |
| 234 | +current page and the number of items per page, then next and previous links in the `Link` header. |
| 235 | + |
| 236 | +```yaml |
| 237 | +paths: |
| 238 | + /stations: |
| 239 | + get: |
| 240 | + summary: Get a list of train stations |
| 241 | + description: Returns a paginated and searchable list of all train stations. |
| 242 | + operationId: get-stations |
| 243 | + responses: |
| 244 | + '200': |
| 245 | + description: A paginated list of train stations. |
| 246 | + headers: |
| 247 | + X-Total-Count: |
| 248 | + description: The total number of items in the collection. |
| 249 | + schema: |
| 250 | + type: integer |
| 251 | + example: 1000 |
| 252 | + X-Page: |
| 253 | + description: The current page number. |
| 254 | + schema: |
| 255 | + type: integer |
| 256 | + example: 2 |
| 257 | + X-Per-Page: |
| 258 | + description: The number of items per page. |
| 259 | + schema: |
| 260 | + type: integer |
| 261 | + example: 10 |
| 262 | + Links: |
| 263 | + description: A set of hypermedia links which serve as controls for the client. |
| 264 | + type: string |
| 265 | + example: | |
| 266 | + <https://api.example.com/stations?page=2>; rel="self", |
| 267 | + <https://api.example.com/stations?page=3>; rel="next", |
| 268 | + <https://api.example.com/stations?page=1>; rel="prev" |
| 269 | + |
| 270 | +``` |
| 271 | + |
| 272 | +However the API is doing pagination, OpenAPI can describe it. The most |
| 273 | +important thing is to be consistent and clear about how pagination works in the |
| 274 | +API reference documentation, and if possible write a custom guide for explaining pagination in an API more specifically so that API consumers can get it right. |
| 275 | + |
| 276 | +## Speakeasy SDK pagination |
| 277 | +Speakeasy SDKs support pagination out of the box, and can be configured to |
| 278 | +automatically handle pagination for any API, allowing clients to focus on working with data instead of learning about specific pagination strategies. |
| 279 | + |
| 280 | +To configure pagination, add the `x-speakeasy-pagination` extension to the OpenAPI description: |
| 281 | + |
| 282 | +```yaml |
| 283 | +/stations: |
| 284 | + get: |
| 285 | + parameters: |
| 286 | + - name: page |
| 287 | + in: query |
| 288 | + schema: |
| 289 | + type: integer |
| 290 | + required: true |
| 291 | + responses: |
| 292 | + "200": |
| 293 | + description: OK |
| 294 | + content: |
| 295 | + application/json: |
| 296 | + schema: |
| 297 | + type: object |
| 298 | + properties: |
| 299 | + data: |
| 300 | + type: array |
| 301 | + items: |
| 302 | + type: integer |
| 303 | + required: |
| 304 | + - data |
| 305 | + x-speakeasy-pagination: |
| 306 | + type: offsetLimit |
| 307 | + inputs: |
| 308 | + - name: page |
| 309 | + in: parameters |
| 310 | + type: page |
| 311 | + outputs: |
| 312 | + results: $.data |
| 313 | +``` |
| 314 | + |
| 315 | +The `x-speakeasy-pagination` configuration supports `offsetLimit`, `cursor`, and `url` implementations of pagination, and allows the generated SDKs to extract the proper response data from the API instead of having to split up data and metadata manually. |
| 316 | + |
| 317 | +Learn more about pagination with Speakeasy SDKs [here](/docs/customize/runtime/pagination). |
0 commit comments