# Registrar un conteo de inventario

Registra lo que hay físicamente en una bodega en un instante (counted_at):

POST `/public/v1/inventory/counts`

Permiso `inventory:write` · `Idempotency-Key` obligatorio · Inventario

el stock oficial pasa a ser esa cantidad y las ventas ocurridas después del conteo se suman encima, nunca se pierden. Un conteo más viejo que el último registrado queda solo como historial (`status: historical`). Solo cuando la cuenta ya lleva el stock en la nube; si no, 409 STOCK_MODE_LEGACY. Emite el webhook `inventory.count.created`.

## Parámetros

### Encabezados

| Nombre            | Tipo   | Requerido | Descripción                                                                                                                                                                                             |
| ----------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Idempotency-Key` | string | Sí        | Obligatorio en todo POST. Un valor único por operación (p. ej. un UUID); reutilízalo solo para reintentar la MISMA operación. Válido 24 h. Máximo 255 caracteres. Formato: `^[A-Za-z0-9_\-:.]{1,255}$`. |

## Cuerpo de la petición

JSON (`Content-Type: application/json`): un objeto [StockCountCreate](https://developers.posdata.so/referencia/objetos/#obj-stockcountcreate).

Un campo que no esté en la lista se rechaza con `VALIDATION_ERROR`.

| Campo              | Tipo               | Requerido | Descripción                                                                                                                                                                         |
| ------------------ | ------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `product_id`       | string (uuid)      | Sí        | —                                                                                                                                                                                   |
| `variant_id`       | string (uuid)      | No        | Obligatorio si el producto tiene variantes.                                                                                                                                         |
| `warehouse_id`     | string (uuid)      | No        | Por defecto, la bodega predeterminada (o la única bodega activa).                                                                                                                   |
| `counted_quantity` | number             | Sí        | Lo que hay físicamente, en la unidad base del producto. Entero, salvo en productos que se venden por peso (`sold_by_weight`), que aceptan decimales. Mínimo: 0. Máximo: 1000000000. |
| `counted_at`       | string (date-time) | No        | CUÁNDO se contó (no cuándo se envía), ISO 8601 con zona. Nunca en el futuro. Por defecto, ahora. Las ventas ocurridas después se suman encima.                                      |
| `note`             | string             | No        | Nota visible en el historial del conteo. Máximo 500 caracteres.                                                                                                                     |

## Ejemplo de petición

El cuerpo del ejemplo lleva solo los campos obligatorios, con valores ilustrativos: ajústalos a tus datos.

**curl**

```sh
curl -X POST "https://api.posdata.so/public/v1/inventory/counts" \
  -H "Authorization: Bearer $POSDATA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"product_id":"3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d","counted_quantity":5}'
```

**Node.js**

```js
const res = await fetch('https://api.posdata.so/public/v1/inventory/counts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSDATA_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(), // reutilízala solo para reintentar
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "product_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
    "counted_quantity": 5
  }),
});
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 uuid

import requests

res = requests.post(
    "https://api.posdata.so/public/v1/inventory/counts",
    headers={
        "Authorization": f"Bearer {os.environ['POSDATA_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),  # reutilízala solo para reintentar
    },
    json={
        "product_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
        "counted_quantity": 5,
    },
    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/counts');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('POSDATA_API_KEY'),
        'Idempotency-Key: ' . bin2hex(random_bytes(16)), // reutilízala solo para reintentar
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'product_id' => '3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d',
        'counted_quantity' => 5,
    ]),
]);
$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']})");
}
```

> **Idempotency-Key**
>
> Usa un valor nuevo por operación y repite el mismo solo para reintentar esa misma operación. Mira [Idempotencia](https://developers.posdata.so/idempotencia/).

## Respuesta

`201` Creado: un objeto [StockCount](https://developers.posdata.so/referencia/objetos/#obj-stockcount).

| Campo               | Tipo                  | Requerido | Descripción                                                                                                                                                                                                                                                                                                                                                  |
| ------------------- | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `object`            | `"stock_count"`       | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `id`                | string (uuid)         | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `warehouse_id`      | string (uuid)         | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `product_id`        | string (uuid)         | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `variant_id`        | string (uuid) \| null | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `counted_quantity`  | number                | Sí        | Lo que había físicamente.                                                                                                                                                                                                                                                                                                                                    |
| `counted_at`        | string (date-time)    | Sí        | Cuándo se contó.                                                                                                                                                                                                                                                                                                                                             |
| `source`            | string                | Sí        | count, count_sheet, initial, upgrade, migration o api.                                                                                                                                                                                                                                                                                                       |
| `status`            | string                | Sí        | `applied`: es la base del stock oficial desde su `counted_at`. `historical`: se guardó solo como historial (ya había un conteo más reciente). `pending`: la cuenta todavía no lleva el stock en la nube (una caja lo registró antes del cambio); se tendrá en cuenta cuando la cuenta pase al stock en la nube. Valores: `applied`, `historical`, `pending`. |
| `estimated`         | boolean               | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `expected_quantity` | number \| null        | Sí        | Lo que el sistema creía en el instante del conteo.                                                                                                                                                                                                                                                                                                           |
| `difference`        | number \| null        | Sí        | `counted_quantity − expected_quantity`.                                                                                                                                                                                                                                                                                                                      |
| `note`              | string \| null        | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |
| `created_at`        | string (date-time)    | Sí        | —                                                                                                                                                                                                                                                                                                                                                            |

```json
{
  "object": "stock_count",
  "id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "warehouse_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "product_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "variant_id": "3f1c2e8d-7a10-4d0b-9a57-0b6f2f0e5c1d",
  "counted_quantity": 5,
  "counted_at": "2026-09-28T15:04:05.000Z",
  "source": "texto",
  "status": "applied",
  "estimated": true,
  "expected_quantity": 5,
  "difference": 1,
  "note": "texto",
  "created_at": "2026-09-28T15:04:05.000Z"
}
```

## 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

### 409Conflicto (CONFLICT, SKU_ALREADY_EXISTS, IDEMPOTENCY_IN_PROGRESS, NO_WAREHOUSE, DEVICES_OUTDATED, INSUFFICIENT_STOCK con `available`, STOCK_MODE_LEGACY).

**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/).

CONFLICTSKU_ALREADY_EXISTSIDEMPOTENCY_IN_PROGRESSNO_WAREHOUSEDEVICES_OUTDATEDINSUFFICIENT_STOCKSTOCK_MODE_LEGACY

### 422Regla de negocio o idempotencia (IDEMPOTENCY_KEY_REUSED, COMPOSITE_STOCK_UNSUPPORTED).

**Respuesta 422**

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/).

IDEMPOTENCY_KEY_REUSEDCOMPOSITE_STOCK_UNSUPPORTED

### 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/createstockcount/ · Índice para agentes: https://developers.posdata.so/llms.txt
