¿Cómo describe OpenAPI la paginación en las respuestas de una API? #
La paginación es uno de los patrones más comunes en las API REST. Cualquier endpoint que devuelva una colección de recursos (usuarios, pedidos, productos, eventos) debe contemplar el caso en que esa colección sea mayor de lo que conviene devolver en una sola respuesta. OpenAPI no impone un único patrón de paginación, pero ofrece las herramientas de esquemas y parámetros necesarias para describir con precisión cualquier enfoque de paginación. Este artículo recorre cómo describir en documentos OpenAPI los estilos de paginación más habituales.
Por qué importa la paginación en el diseño de API #
Sin paginación, las API que devuelven grandes conjuntos de datos obligan a los clientes a recibir potencialmente millones de registros en una sola respuesta, lo que agota la memoria, el ancho de banda de red y los márgenes de tiempo de espera. La paginación divide los resultados en páginas manejables y permite a los clientes solicitarlas de forma incremental. Desde la perspectiva de OpenAPI, la paginación afecta a:
- Parámetros de consulta: las entradas que los clientes utilizan para controlar qué página reciben.
- Esquemas del cuerpo de la respuesta: la forma del contenedor paginado, que incluye el arreglo (array) de datos y metadatos como los totales y los cursores.
- Encabezados de respuesta: algunas API comunican los metadatos de paginación mediante encabezados en lugar del cuerpo.
- Encabezados Link: el encabezado HTTP
Link(RFC 5988) es una forma estándar de comunicar las URL de la página siguiente y la anterior.
Paginación basada en desplazamiento (offset) #
La paginación por desplazamiento es el enfoque más sencillo: los clientes proporcionan un offset (cuántos registros omitir) y un limit (cuántos registros devolver).
Parámetros #
parameters:
- name: limit
in: query
description: Maximum number of results to return (1–100)
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: offset
in: query
description: Number of results to skip before returning results
schema:
type: integer
minimum: 0
default: 0
Esquema de respuesta #
components:
schemas:
UserListResponse:
type: object
required: [data, total, limit, offset]
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
total:
type: integer
description: Total number of users matching the query
example: 1523
limit:
type: integer
description: Maximum number of results per page
example: 20
offset:
type: integer
description: Number of results skipped
example: 40
La paginación por desplazamiento es fácil de implementar y de entender, pero tiene inconvenientes bien conocidos: si se insertan o eliminan registros entre una página y otra, los clientes pueden ver duplicados o perder registros. Para la mayoría de los casos de uso, sigue siendo una opción pragmática.
Paginación por páginas #
La paginación por páginas utiliza los parámetros page (número de página, empezando en 1) y per_page (tamaño de página) en lugar de desplazamientos directos.
Parámetros #
parameters:
- name: page
in: query
description: Page number (1-indexed)
schema:
type: integer
minimum: 1
default: 1
- name: per_page
in: query
description: Number of results per page
schema:
type: integer
minimum: 1
maximum: 100
default: 25
Esquema de respuesta con metadatos de paginación #
components:
schemas:
PaginationMeta:
type: object
required: [current_page, per_page, total_pages, total_count]
properties:
current_page:
type: integer
example: 2
per_page:
type: integer
example: 25
total_pages:
type: integer
example: 61
total_count:
type: integer
example: 1523
Paginación basada en cursor #
La paginación basada en cursor (o keyset) es el enfoque más escalable para conjuntos de datos grandes que se actualizan con frecuencia. En lugar de un desplazamiento, la API devuelve un cursor (un token opaco que representa la posición en el conjunto de datos) y el cliente lo envía de vuelta para obtener la página siguiente.
Parámetros #
parameters:
- name: cursor
in: query
description: >
Opaque cursor returned in the previous response. Omit for the first page.
schema:
type: string
example: "eyJpZCI6MTAwfQ=="
- name: limit
in: query
description: Maximum number of results to return
schema:
type: integer
minimum: 1
maximum: 100
default: 20
Esquema de respuesta #
components:
schemas:
UserCursorResponse:
type: object
required: [data]
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
next_cursor:
type: string
nullable: true
description: >
Cursor for the next page. Null if this is the last page.
example: "eyJpZCI6MTIwfQ=="
has_more:
type: boolean
description: Whether there are more results after this page
example: true
El cursor es opaco para el cliente: no debe interpretarse ni construirse. Es importante documentarlo con claridad en los campos description.
Paginación mediante encabezados Link #
Muchas API (incluida la API de GitHub) utilizan el encabezado HTTP Link (RFC 5988) para comunicar las URL de paginación en lugar de incluirlas en el cuerpo de la respuesta.
Documentar encabezados Link en OpenAPI #
responses:
"200":
description: A paginated list of repositories
headers:
Link:
description: >
Pagination links. May include `next`, `prev`, `first`, and `last` relations.
Example: `<https://api.example.com/users?page=3>; rel="next",
<https://api.example.com/users?page=1>; rel="first"`
schema:
type: string
X-Total-Count:
description: Total number of results across all pages
schema:
type: integer
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
Componentes de paginación reutilizables #
Para las API con varios endpoints paginados, definir parámetros y esquemas reutilizables en components evita repeticiones y mantiene el documento coherente:
components:
parameters:
LimitParam:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
CursorParam:
name: cursor
in: query
schema:
type: string
schemas:
CursorPage:
type: object
required: [data]
properties:
data:
type: array
items: {}
next_cursor:
type: string
nullable: true
has_more:
type: boolean
Las operaciones individuales hacen referencia a estos componentes compartidos:
paths:
/users:
get:
parameters:
- $ref: '#/components/parameters/LimitParam'
- $ref: '#/components/parameters/CursorParam'
responses:
"200":
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CursorPage'
- properties:
data:
items:
$ref: '#/components/schemas/User'
Documentar el comportamiento de la paginación en las descripciones #
Las anotaciones de los esquemas por sí solas no describen por completo la semántica de la paginación. Los campos description de las operaciones y de los parámetros deben explicar:
- Qué estilo de paginación se utiliza y por qué.
- El comportamiento de los cursores: si caducan y durante cuánto tiempo siguen siendo válidos.
- El orden de clasificación: la paginación normalmente solo tiene sentido cuando los resultados se encuentran en un orden estable y determinista.
- El tamaño máximo de página: qué ocurre si el cliente solicita más del máximo.
- Los resultados vacíos: si un arreglo
datavacío o un cursor nulo indica el final de los resultados.
Estándares del sector: JSON:API y HAL #
Algunos equipos adoptan formatos de contenedor estandarizados que incluyen la paginación:
- JSON:API define un objeto
linkscon las URLfirst,last,prevynext, y un objetometapara los totales. - HAL (Hypertext Application Language) incorpora los enlaces de paginación en
_links.
Ambos se pueden describir en OpenAPI mediante sus respectivos esquemas en components.
Conclusión #
Las herramientas de parámetros y esquemas de OpenAPI cubren con precisión todos los patrones de paginación habituales: por desplazamiento, por páginas y basada en cursor. La clave es la coherencia: elegir un único estilo de paginación para todos los endpoints de colecciones, definir parámetros y esquemas de respuesta reutilizables en components e invertir en descripciones claras que expliquen no solo qué hacen los parámetros, sino cómo funciona la paginación en su conjunto. Una paginación bien documentada es una de las mejoras con más impacto que un equipo de API puede hacer en la experiencia del desarrollador.
Última actualización: 30 de abril de 2026.