diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..13198b4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## Unreleased + +### Paginación por cursores (`pagination=cursor`) + +- Nuevo modo de paginación opt-in `pagination=cursor`, **recomendado** para + listas grandes: recorre todos los resultados sin el tope de 30 páginas del + modo `page`, con páginas estables aunque cambien los datos. +- Nuevos parámetros `after` y `before` para navegar entre páginas de forma + secuencial. Cada búsqueda regresa `previous_cursor` y `next_cursor`. +- Nuevo campo `totals_are_capped` en las respuestas de búsqueda de ambos modos: + cuando hay más de 3,000 resultados, `total_results` regresa `3000` y + `totals_are_capped` es `true` ("más de 3,000"). +- La paginación por páginas (`pagination=page`, el modo por defecto) queda + limitada a 30 páginas. diff --git a/website/docs/guides/pagination.mdx b/website/docs/guides/pagination.mdx new file mode 100644 index 000000000..e27598c21 --- /dev/null +++ b/website/docs/guides/pagination.mdx @@ -0,0 +1,153 @@ +--- +sidebar_position: 11 +--- + +# Paginación + +Todos los listados de la API devuelven resultados paginados. Facturapi ofrece dos +modos de paginación que puedes elegir con el parámetro `pagination`: + +- **`pagination=page`** — el modo clásico, por **páginas numeradas**. Es el + comportamiento por defecto cuando no se envía `pagination`. +- **`pagination=cursor`** — el modo recomendado, por **cursores**. Recorre los + resultados de forma secuencial, sin el tope de 30 páginas del modo `page`, y + con páginas estables aunque cambien los datos. + +## ¿Qué modo usar? + +| | `pagination=page` | `pagination=cursor` | +| --- | --- | --- | +| Navegación | Saltar a una página específica (`page=3`) | Secuencial: siguiente / anterior | +| Alcance | Hasta 3,000 resultados (30 páginas × 100) | Todos los resultados de la búsqueda | +| Estabilidad | Las páginas se corren si cambian los datos | Páginas estables (basadas en posición) | +| Uso recomendado | Compatibilidad, interfaces con saltos de página | Listas grandes, iteración, exportaciones | + +Para la mayoría de los casos, y en particular para listas grandes, te recomendamos +usar `pagination=cursor`. + +La diferencia práctica más importante entre ambos modos es el **alcance**: +`pagination=page` solo permite acceder a los primeros 3,000 resultados +(30 páginas × 100 por página), mientras que `pagination=cursor` puede recorrer +**todos** los resultados de la búsqueda, incluso cuando `total_results` está +capeado a 3,000. Si necesitas exportar o procesar más de 3,000 resultados, usa +`pagination=cursor`. + +## Parámetros comunes + +| Parámetro | Tipo | Descripción | +| --- | --- | --- | +| `limit` | `integer` | Cantidad máxima de resultados a regresar, del 1 al 100. | +| `pagination` | `string` | `page` (por defecto) o `cursor`. | + +Los parámetros `page`, `after` y `before` dependen del modo elegido y son +**mutuamente excluyentes** entre modos: + +- `pagination=page` acepta `page`, pero no `after` ni `before`. +- `pagination=cursor` acepta `after` o `before`, pero no `page`. + +Enviar `after` y `before` al mismo tiempo devuelve un error. + +## Paginación por páginas (`pagination=page`) + +Es el modo por defecto. Además de `limit`, acepta: + +| Parámetro | Descripción | +| --- | --- | +| `page` | Número de página a regresar, empezando desde 1. La página máxima es 30. | + +Respuesta: + +```json +{ + "data": [ ... ], + "page": 1, + "total_pages": 3, + "total_results": 250, + "totals_are_capped": false +} +``` + +## Paginación por cursores (`pagination=cursor`) + +En lugar de un número de página, cada respuesta regresa dos cursores que te +permiten avanzar o retroceder sobre los resultados: + +| Parámetro | Descripción | +| --- | --- | +| `after` | Devuelve los resultados posteriores al cursor indicado. | +| `before` | Devuelve los resultados anteriores al cursor indicado. | + +Respuesta: + +```json +{ + "data": [ ... ], + "previous_cursor": null, + "next_cursor": "d176717f4000000.i65a8282775d4e9263da48115", + "total_results": 250, + "totals_are_capped": false +} +``` + +`total_results` y `totals_are_capped` se incluyen únicamente en la **primera +página** de la búsqueda (cuando no se envían `after` ni `before`): el total no +cambia entre páginas, así que las siguientes solo regresan `data` y los cursores. + +### Navegar entre páginas + +1. **Primera página:** se obtiene sin enviar `after` ni `before`. + `previous_cursor` regresa `null`, indicando que no hay páginas anteriores. +2. **Página siguiente:** envía `after` con el valor de `next_cursor` de la + respuesta anterior. +3. **Página anterior:** envía `before` con el valor de `previous_cursor` de la + respuesta anterior. + +La presencia de `previous_cursor` y `next_cursor` es la única fuente de verdad +para saber si hay más resultados: si `next_cursor` es `null`, llegaste al final +de la lista; si `previous_cursor` es `null`, estás en la primera página. + +```bash +# Primera página +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" + +# Página siguiente (usa next_cursor de la respuesta anterior) +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10&after=NEXT_CURSOR" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" + +# Página anterior (usa previous_cursor de la respuesta anterior) +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10&before=PREVIOUS_CURSOR" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" +``` + +Los cursores son tokens opacos y específicos de la búsqueda que los generó: no +los combines con otra búsqueda ni los compartas entre distintas consultas. +Usarlos con una búsqueda diferente puede regresar resultados inesperados. + +## Totales y límite de 3,000 + +Ambos modos regresan `total_results` con el número de elementos que coinciden con +la búsqueda, **con un tope de 3,000**: + +- Si hay 3,000 o menos resultados, `total_results` es exacto y + `totals_are_capped` es `false`. +- Si hay más de 3,000, `total_results` regresa `3000` y `totals_are_capped` es + `true`, indicando que el total real es "más de 3,000". + +```json +{ + "data": [ ... ], + "previous_cursor": "d176717f4000000.i65a8282775d4e9263da48115", + "next_cursor": null, + "total_results": 3000, + "totals_are_capped": true +} +``` + +En ambos modos `total_results` es informativo: el conteo se acota a 3,000 y no +recorre toda la colección. La ventaja práctica de `pagination=cursor` no es la +velocidad del conteo (ambos modos lo acotan), sino su **alcance** y la +**estabilidad** de las páginas. diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/guides/pagination.mdx b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/pagination.mdx new file mode 100644 index 000000000..48f320884 --- /dev/null +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/pagination.mdx @@ -0,0 +1,152 @@ +--- +sidebar_position: 11 +--- + +# Pagination + +All API listing endpoints return paginated results. Facturapi offers two +pagination modes that you can choose with the `pagination` parameter: + +- **`pagination=page`** — the classic mode, based on **numbered pages**. This is + the default behavior when `pagination` is not sent. +- **`pagination=cursor`** — the recommended mode, based on **cursors**. It + traverses results sequentially, without the 30-page limit of `page` mode, and + with stable pages even when the data changes. + +## Which mode should I use? + +| | `pagination=page` | `pagination=cursor` | +| --- | --- | --- | +| Navigation | Jump to a specific page (`page=3`) | Sequential: next / previous | +| Reach | Up to 3,000 results (30 pages × 100) | All search results | +| Stability | Pages shift when the data changes | Stable pages (position-based) | +| Recommended for | Compatibility, UIs with page jumps | Large lists, iteration, exports | + +For most cases, and especially for large lists, we recommend using +`pagination=cursor`. + +The most important practical difference between both modes is **reach**: +`pagination=page` only gives access to the first 3,000 results +(30 pages × 100 per page), while `pagination=cursor` can traverse **all** the +search results, even when `total_results` is capped at 3,000. If you need to +export or process more than 3,000 results, use `pagination=cursor`. + +## Common parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| `limit` | `integer` | Maximum number of results to return, from 1 to 100. | +| `pagination` | `string` | `page` (default) or `cursor`. | + +The `page`, `after` and `before` parameters depend on the selected mode and are +**mutually exclusive** between modes: + +- `pagination=page` accepts `page`, but not `after` or `before`. +- `pagination=cursor` accepts `after` or `before`, but not `page`. + +Sending `after` and `before` at the same time returns an error. + +## Page pagination (`pagination=page`) + +This is the default mode. In addition to `limit`, it accepts: + +| Parameter | Description | +| --- | --- | +| `page` | Page number to return, starting from 1. The maximum page is 30. | + +Response: + +```json +{ + "data": [ ... ], + "page": 1, + "total_pages": 3, + "total_results": 250, + "totals_are_capped": false +} +``` + +## Cursor pagination (`pagination=cursor`) + +Instead of a page number, each response returns two cursors that let you move +forward or backward through the results: + +| Parameter | Description | +| --- | --- | +| `after` | Returns the results after the given cursor. | +| `before` | Returns the results before the given cursor. | + +Response: + +```json +{ + "data": [ ... ], + "previous_cursor": null, + "next_cursor": "d176717f4000000.i65a8282775d4e9263da48115", + "total_results": 250, + "totals_are_capped": false +} +``` + +`total_results` and `totals_are_capped` are only included on the **first page** +of the search (when neither `after` nor `before` is sent): the total does not +change between pages, so subsequent pages only return `data` and the cursors. + +### Navigating between pages + +1. **First page:** obtained by not sending `after` or `before`. + `previous_cursor` returns `null`, meaning there are no previous pages. +2. **Next page:** send `after` with the `next_cursor` value from the previous + response. +3. **Previous page:** send `before` with the `previous_cursor` value from the + previous response. + +The presence of `previous_cursor` and `next_cursor` is the only source of truth +for whether there are more results: if `next_cursor` is `null`, you reached the +end of the list; if `previous_cursor` is `null`, you are on the first page. + +```bash +# First page +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" + +# Next page (use next_cursor from the previous response) +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10&after=NEXT_CURSOR" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" + +# Previous page (use previous_cursor from the previous response) +curl "https://www.facturapi.io/v2/invoices?pagination=cursor&limit=10&before=PREVIOUS_CURSOR" \ + -G \ + -H "Authorization: Bearer sk_test_API_KEY" +``` + +Cursors are opaque tokens tied to the search that generated them: do not combine +them with a different search or share them between queries. Using them with a +different search may return unexpected results. + +## Totals and the 3,000 limit + +Both modes return `total_results` with the number of elements matching the +search, **capped at 3,000**: + +- If there are 3,000 or fewer results, `total_results` is exact and + `totals_are_capped` is `false`. +- If there are more than 3,000, `total_results` returns `3000` and + `totals_are_capped` is `true`, indicating the real total is "more than 3,000". + +```json +{ + "data": [ ... ], + "previous_cursor": "d176717f4000000.i65a8282775d4e9263da48115", + "next_cursor": null, + "total_results": 3000, + "totals_are_capped": true +} +``` + +In both modes, `total_results` is informational and the count is capped at +3,000 without traversing the whole collection. The practical advantage of +`pagination=cursor` is not count speed (both modes cap it), but **reach** and +**stable pages**. diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 4948857b8..7bd6a063b 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -1038,6 +1038,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1139,6 +1143,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1222,6 +1230,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1317,6 +1329,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1400,6 +1416,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1483,6 +1503,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1566,6 +1590,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1649,6 +1677,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1732,6 +1764,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1829,6 +1865,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1912,6 +1952,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2141,6 +2185,10 @@ paths: "page" => 1 ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2771,6 +2819,10 @@ paths: "page" => 1 ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -3335,6 +3387,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -4772,6 +4828,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -6100,6 +6160,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -6727,6 +6791,10 @@ paths: $organizations = $facturapi->Organizations->all() parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -10677,6 +10745,10 @@ paths: "q" => "ukelele" ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -10760,6 +10832,10 @@ paths: "q" => "pulgada" ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -11306,6 +11382,31 @@ components: maximum: 100 description: Number from 1 to 100 representing the maximum amount of results to return for pagination purposes. + SearchPagination: + in: query + name: pagination + schema: + type: string + enum: + - page + - cursor + description: | + Pagination mode for the search. `page` (default) or `cursor` (recommended for large lists). + SearchAfter: + in: query + name: after + schema: + type: string + description: | + Returns the results after the given cursor. Only with `pagination=cursor`; mutually exclusive with `before`. + SearchBefore: + in: query + name: before + schema: + type: string + description: | + Returns the results before the given cursor. Only with `pagination=cursor`; mutually exclusive with `after`. + schemas: RelatedResourceMessage: type: object @@ -11491,7 +11592,24 @@ components: type: integer example: 1 title: Total results - description: The total number of results available in the search + description: | + The total number of results available in the search. In `pagination=cursor` mode it is only included on the first page of the search (no `after`/`before`); the total does not change between pages. + previous_cursor: + type: string + example: null + title: Previous cursor + description: Cursor to fetch the previous page of results. It is `null` on the first page. Only available with `pagination=cursor`. + next_cursor: + type: string + example: null + title: Next cursor + description: Cursor to fetch the next page of results. It is `null` when there are no more results. Only available with `pagination=cursor`. + totals_are_capped: + type: boolean + example: false + title: Totals capped + description: Indicates whether `total_results` is capped at 3,000 because there are more results than reported. + ResourceAutoGeneratedProps: type: object required: diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index ed060b82a..e206bca99 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -1009,6 +1009,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1118,6 +1122,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1227,6 +1235,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1336,6 +1348,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1445,6 +1461,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1554,6 +1574,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1663,6 +1687,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1772,6 +1800,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -1881,6 +1913,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2004,6 +2040,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2113,6 +2153,10 @@ paths: ["limit"] = 10 }); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2369,6 +2413,10 @@ paths: "page" => 1 ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -2999,6 +3047,10 @@ paths: "page" => 1 ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -3561,6 +3613,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -4992,6 +5048,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -6316,6 +6376,10 @@ paths: limit => 10, ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -6945,6 +7009,10 @@ paths: $organizations = $facturapi->Organizations->all() parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -10883,6 +10951,10 @@ paths: "q" => "ukelele" ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -10966,6 +11038,10 @@ paths: "q" => "pulgada" ]); parameters: + - $ref: "#/components/parameters/SearchPagination" + - $ref: "#/components/parameters/SearchAfter" + - $ref: "#/components/parameters/SearchBefore" + - in: query name: q schema: @@ -11512,6 +11588,31 @@ components: maximum: 100 description: Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. + SearchPagination: + in: query + name: pagination + schema: + type: string + enum: + - page + - cursor + description: | + Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). + SearchAfter: + in: query + name: after + schema: + type: string + description: | + Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. + SearchBefore: + in: query + name: before + schema: + type: string + description: | + Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. + schemas: SearchKeyDescriptionResult: type: object @@ -11726,7 +11827,24 @@ components: type: integer example: 1 title: Resultados totales - description: Número de elementos individuales en todas las páginas de resultados + description: | + Número de elementos individuales en todas las páginas de resultados. En modo `pagination=cursor` solo se incluye en la primera página de la búsqueda (sin `after`/`before`); el total no cambia entre páginas. + previous_cursor: + type: string + example: null + title: Cursor anterior + description: Cursor para obtener la página anterior de resultados. Es `null` en la primera página. Solo disponible con `pagination=cursor`. + next_cursor: + type: string + example: null + title: Cursor siguiente + description: Cursor para obtener la página siguiente de resultados. Es `null` cuando no hay más resultados. Solo disponible con `pagination=cursor`. + totals_are_capped: + type: boolean + example: false + title: Resultados con tope + description: Indica si `total_results` está limitado a 3,000 porque existen más resultados de los reportados. + ResourceAutoGeneratedProps: type: object required: