> ## Documentation Index
> Fetch the complete documentation index at: https://muveya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Paginación

> Recorre las listas paginadas de /v1 con limit y un cursor opaco, y conoce el orden y los filtros de cada lista.

Toda lista de `/v1` devuelve el mismo sobre, esté paginada o no:

```json theme={null}
{
  "data": [],
  "hasMore": true,
  "nextCursor": "NjZkMGExZjBjMGZmZWUwMDAwMDAwODAy"
}
```

| Campo | Significado |
| - | - |
| `data` | Los elementos de esta página. |
| `hasMore` | `true` cuando hay al menos una página más. |
| `nextCursor` | El token de la página siguiente. Aparece **solo** cuando `hasMore` es `true`; si no, el campo se omite (no es `null`). |

## Qué listas están paginadas

| Operación | Paginada | Orden | Filtros |
| - | - | - | - |
| `GET /v1/orders` | Sí | Del más antiguo al más reciente, por fecha de creación | `status` |
| `GET /v1/fulfillments` | Sí | Del más antiguo al más reciente, por fecha de creación | `status` |
| `GET /v1/inventory/balances` | Sí | Por id de caja, ascendente | `catalogItemId`, `warehouseId` |
| `GET /v1/clinics` | No, una sola página | Orden de creación | Ninguno |
| `GET /v1/warehouses` | No, una sola página | Orden de creación | Ninguno |
| `GET /v1/catalog/items` | No, una sola página | Orden de creación | Ninguno |
| `GET /v1/catalog/categories` | No, una sola página | Orden de creación | Ninguno |
| `GET /v1/inventory/boxes/{boxId}/movements` | No, una sola página | Del más antiguo al más reciente, por `occurredAt` | Ninguno |

Las listas no paginadas devuelven todos los elementos en una sola página con `"hasMore": false` e ignoran `limit` y `cursor`. De todos modos, usa el mismo ciclo para todas las listas: si alguna pasa a estar paginada, tu código seguirá funcionando sin cambios.

## Parámetros de consulta

<ParamField query="limit" type="integer" default="50">
  Cuántos elementos devolver, de 1 a 200. Solo se aceptan dígitos. Un 0, un número negativo, un decimal, un texto o un número mayor que 200 se rechaza con `400` y el código `common.invalid_request`.
</ParamField>

<ParamField query="cursor" type="string">
  El `nextCursor` de la página anterior, copiado tal cual. Un cursor que no se puede leer se rechaza con `400` y el código `common.invalid_request`; el servidor nunca vuelve en silencio a la primera página.
</ParamField>

<ParamField query="status" type="string">
  Solo en `GET /v1/orders` y `GET /v1/fulfillments`. Debe ser uno de los valores de estado indicados para esa operación en el grupo **Endpoints** (por ejemplo `pending_approval` para pedidos o `dispatched` para registros de entrega). Cualquier otro valor se rechaza con `400` y el código `common.invalid_request`.
</ParamField>

<ParamField query="catalogItemId" type="string">
  Solo en `GET /v1/inventory/balances`: conserva las cajas de un insumo del catálogo. Un id que no coincide con nada devuelve una página vacía.
</ParamField>

<ParamField query="warehouseId" type="string">
  Solo en `GET /v1/inventory/balances`: conserva las cajas de una bodega. Un id que no coincide con nada devuelve una página vacía.
</ParamField>

## Recorre todas las páginas

<CodeGroup>
  ```bash cURL theme={null}
  # First page
  curl "https://api.muveya.com/v1/inventory/balances?limit=200&warehouseId=66d0a1f0c0ffee0000000201" \
    -H "Authorization: Bearer $MUVEYA_API_KEY"

  # Next page: same filters, plus the cursor you received
  curl "https://api.muveya.com/v1/inventory/balances?limit=200&warehouseId=66d0a1f0c0ffee0000000201&cursor=NjZkMGExZjBjMGZmZWUwMDAwMDAwODAy" \
    -H "Authorization: Bearer $MUVEYA_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const BASE_URL = "https://api.muveya.com";
  const headers = { Authorization: `Bearer ${process.env.MUVEYA_API_KEY}` };

  async function* pages(path, filters = {}) {
    let cursor;
    do {
      const url = new URL(path, BASE_URL);
      url.searchParams.set("limit", "200");
      for (const [name, value] of Object.entries(filters)) url.searchParams.set(name, value);
      if (cursor) url.searchParams.set("cursor", cursor);

      const response = await fetch(url, { headers });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      const page = await response.json();
      yield page.data;
      cursor = page.hasMore ? page.nextCursor : undefined;
    } while (cursor);
  }

  let boxes = 0;
  for await (const data of pages("/v1/inventory/balances", { warehouseId: "66d0a1f0c0ffee0000000201" })) {
    boxes += data.length;
  }
  console.log(`${boxes} boxes`);
  ```

  ```python Python theme={null}
  import os
  import requests

  BASE_URL = "https://api.muveya.com"
  session = requests.Session()
  session.headers["Authorization"] = f"Bearer {os.environ['MUVEYA_API_KEY']}"

  def pages(path, **filters):
      cursor = None
      while True:
          params = {"limit": 200, **filters}
          if cursor:
              params["cursor"] = cursor
          response = session.get(f"{BASE_URL}{path}", params=params, timeout=30)
          response.raise_for_status()
          page = response.json()
          yield page["data"]
          if not page["hasMore"]:
              return
          cursor = page["nextCursor"]

  boxes = sum(len(data) for data in pages("/v1/inventory/balances", warehouseId="66d0a1f0c0ffee0000000201"))
  print(f"{boxes} boxes")
  ```
</CodeGroup>

```json Última página theme={null}
{
  "data": [
    {
      "boxId": "66d0a1f0c0ffee0000000803",
      "catalogItemId": "66d0a1f0c0ffee0000000401",
      "warehouseId": "66d0a1f0c0ffee0000000201",
      "onHand": 7,
      "reserved": 2,
      "available": 5
    }
  ],
  "hasMore": false
}
```

## Reglas del cursor

* **El cursor es opaco.** Trátalo como un token. No lo decodifiques, no lo construyas ni lo modifiques; su formato puede cambiar sin aviso.
* **Un cursor marca una posición, no un número de página.** La página siguiente empieza justo después del último elemento que recibiste. Los elementos creados mientras recorres la lista no se saltan ni se repiten: los pedidos y registros de entrega nuevos aparecen al final, porque esas listas van del más antiguo al más reciente.
* **Envía los mismos filtros en cada página.** El cursor guarda solo la posición. Si cambias `status`, `catalogItemId` o `warehouseId` a mitad de camino, la página siguiente aplica los filtros nuevos desde esa posición.
* **Los filtros se aplican en cada solicitud.** Si un pedido cambia de estado mientras recorres `GET /v1/orders?status=approved`, puede salir del filtro antes de que llegues a él.
* **Usa un cursor solo con la operación que lo emitió.** Un cursor de una lista no sirve en otra.
* **Usa `limit=200` para recorridos completos.** Menos páginas, más grandes, consumen menos de tu presupuesto de solicitudes (consulta [Límites de uso](/docs/es/api-reference/rate-limits)).

## Errores

| Estado | `code` | Causa |
| - | - | - |
| `400` | `common.invalid_request` | `limit` fuera de rango o no entero, `cursor` ilegible o `status` desconocido |
| `401` | `api_keys.invalid` | Consulta [Autenticación](/docs/es/api-reference/authentication#respuestas-401-y-403) |
| `403` | `api_keys.scope_missing` | A la clave le falta el scope de la lista (consulta [Scopes](/docs/es/api-reference/scopes)) |
| `429` | `common.too_many_requests` | Consulta [Límites de uso](/docs/es/api-reference/rate-limits) |

## Páginas relacionadas

* [Guía rápida de la API](/docs/es/api-reference/quickstart)
* [Webhooks](/docs/es/api-reference/webhooks): cómo consultar listas periódicamente para mantenerte al día
* [Errores](/docs/es/api-reference/errors)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.