# Kardex de un producto

El libro de un producto con saldo corrido; con inventory:costs también el costo y el valor.

GET `/public/v1/inventory/kardex`

Permiso `inventory:read` · Inventario

Solo cuando la cuenta lleva el stock en la nube (todas sus cajas con la versión nueva de Posdata); antes responde 409 STOCK_MODE_LEGACY.

## Parámetros

### Parámetros de consulta

| Nombre         | Tipo               | Requerido | Descripción                                                                 |
| -------------- | ------------------ | --------- | --------------------------------------------------------------------------- |
| `product_id`   | string (uuid)      | No        | El producto (obligatorio).                                                  |
| `warehouse_id` | string (uuid)      | No        | Solo las filas de esa bodega (el saldo total sigue siendo el del producto). |
| `variant_id`   | string (uuid)      | No        | Solo las filas de esa variante.                                             |
| `from`         | string (date-time) | No        | Desde (instante ISO 8601 con zona). Los saldos ya incluyen lo anterior.     |
| `to`           | string (date-time) | No        | Hasta (instante ISO 8601 con zona).                                         |
| `type`         | string             | No        | Solo ese tipo de fila. Valores: `in`, `out`, `count`, `cost`.               |
| `order`        | string             | No        | Orden cronológico. Valores: `asc`, `desc`. Por defecto: `asc`.              |
| `page`         | integer            | No        | Página (empieza en 1). Por defecto: `1`. Mínimo: 1.                         |
| `page_size`    | integer            | No        | Filas por página (máximo 200). Por defecto: `50`. Mínimo: 1. Máximo: 200.   |

## Ejemplo de petición

**curl**

```sh
curl "https://api.posdata.so/public/v1/inventory/kardex" \
  -H "Authorization: Bearer $POSDATA_API_KEY"
```

**Node.js**

```js
const res = await fetch('https://api.posdata.so/public/v1/inventory/kardex', {
  headers: {
    Authorization: `Bearer ${process.env.POSDATA_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message} (${data.error.request_id})`);
```

**Python**

```python
import os

import requests

res = requests.get(
    "https://api.posdata.so/public/v1/inventory/kardex",
    headers={
        "Authorization": f"Bearer {os.environ['POSDATA_API_KEY']}",
    },
    timeout=30,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{data['error']['code']}: {data['error']['message']} ({data['error']['request_id']})")
```

**PHP**

```php
<?php
$ch = curl_init('https://api.posdata.so/public/v1/inventory/kardex');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('POSDATA_API_KEY'),
    ],
]);
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
    throw new RuntimeException("{$data['error']['code']}: {$data['error']['message']} ({$data['error']['request_id']})");
}
```

## Respuesta

`200` OK: un objeto [InventoryKardex](https://developers.posdata.so/referencia/objetos/#obj-inventorykardex).

| Campo                   | Tipo                  | Requerido | Descripción                                                                              |
| ----------------------- | --------------------- | --------- | ---------------------------------------------------------------------------------------- |
| `object`                | `"kardex"`            | No        | —                                                                                        |
| `product`               | object                | No        | —                                                                                        |
| `product.id`            | string (uuid)         | No        | —                                                                                        |
| `product.local_id`      | string                | No        | —                                                                                        |
| `product.name`          | string                | No        | —                                                                                        |
| `rows`                  | arreglo de object     | No        | —                                                                                        |
| `meta`                  | object                | No        | —                                                                                        |
| `meta.page`             | integer               | No        | —                                                                                        |
| `meta.page_size`        | integer               | No        | —                                                                                        |
| `meta.total`            | integer               | No        | —                                                                                        |
| `meta.total_pages`      | integer               | No        | —                                                                                        |
| `levels`                | arreglo de object     | No        | —                                                                                        |
| `levels[].warehouse_id` | string (uuid)         | No        | —                                                                                        |
| `levels[].variant_id`   | string (uuid) \| null | No        | —                                                                                        |
| `levels[].quantity`     | number                | No        | —                                                                                        |
| `total_qty`             | number                | No        | Existencia total del producto al final de lo consultado.                                 |
| `avg_cost`              | number \| null        | No        | Costo promedio ponderado al final; `null` si se desconoce o no tienes `inventory:costs`. |
| `warehouses`            | arreglo de object     | No        | —                                                                                        |
| `warehouses[].id`       | string (uuid)         | No        | —                                                                                        |
| `warehouses[].name`     | string                | No        | —                                                                                        |
| `costs_hidden`          | boolean               | No        | `true` si tu llave no tiene `inventory:costs`.                                           |

```json
{
  "object": "kardex",
  "product": {
    "id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
    "local_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
    "name": "Café molido 500 g"
  },
  "rows": [
    {}
  ],
  "meta": {
    "page": 1,
    "page_size": 1,
    "total": 25900,
    "total_pages": 25900
  },
  "levels": [
    {
      "warehouse_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
      "variant_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
      "quantity": 5
    }
  ],
  "total_qty": 25900,
  "avg_cost": 1,
  "warehouses": [
    {
      "id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
      "name": "Café molido 500 g"
    }
  ],
  "costs_hidden": true
}
```

## Errores

Todo error responde con un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error) y un `request_id`. Ramifica por `error.code`, nunca por el mensaje.

### 400Petición inválida (VALIDATION_ERROR, INVALID_JSON, INVALID_CURSOR).

**Respuesta 400**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

VALIDATION_ERRORINVALID_JSONINVALID_CURSOR

### 401Sin API key válida (UNAUTHENTICATED, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED, API_KEY_WRONG_ENVIRONMENT, ACCOUNT_INACTIVE).

**Respuesta 401**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

UNAUTHENTICATEDINVALID_API_KEYAPI_KEY_REVOKEDAPI_KEY_EXPIREDAPI_KEY_WRONG_ENVIRONMENTACCOUNT_INACTIVE

### 403Sin permiso (PLAN_REQUIRED, INSUFFICIENT_SCOPE, BROWSER_REQUESTS_NOT_ALLOWED).

**Respuesta 403**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

PLAN_REQUIREDINSUFFICIENT_SCOPEBROWSER_REQUESTS_NOT_ALLOWED

### 409STOCK_MODE_LEGACY: la cuenta todavía no lleva el stock en la nube (sus cajas aún en una versión anterior de Posdata).

**Respuesta 409**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

STOCK_MODE_LEGACY

### 429Límite de peticiones (RATE_LIMITED). Respeta Retry-After.

**Respuesta 429**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

RATE_LIMITED

### 500Error interno (INTERNAL_ERROR). Reintenta y comparte el request_id si persiste.

**Respuesta 500**

Cuerpo: un objeto [Error](https://developers.posdata.so/referencia/objetos/#obj-error). Qué significa cada código y qué hacer: [Errores](https://developers.posdata.so/errores/).

INTERNAL_ERROR

---

Fuente: https://developers.posdata.so/referencia/operations/getinventorykardex/ · Índice para agentes: https://developers.posdata.so/llms.txt
