Inventario
Lee y ajusta el stock de cada variante en varias ubicaciones con Admin API.
Lleva el stock del «Polo de algodón» en Almacén Lima y Tienda Miraflores: lee cada nivel, ajústalo sin pisar cambios ajenos y actualiza muchos niveles en un lote.
Requisitos
- Un token de Admin en
ECOMIQ_TOKEN. Ver Inicio rápido. - Los permisos
settings:readpara las ubicaciones,inventory:readpara leer stock einventory:updatepara cambiarlo. - Variantes con
trackInventory: truey al menos una ubicación activa.
Lista las ubicaciones
Pide las ubicaciones activas: solo ellas aceptan ajustes de stock. Guarda el
id de cada una junto a su code.
curl "https://api.ecomiq.pe/api/v1/fulfillment/locations?isActive=true&limit=50" \
-H "Authorization: Bearer $ECOMIQ_TOKEN"{
"data": [
{
"id": "7204558912004325378",
"name": "Almacén Lima",
"code": "ALM-LIMA",
"type": "warehouse",
"isActive": true,
"isDefault": true
},
{
"id": "7204558912004325380",
"name": "Tienda Miraflores",
"code": "TDA-MIRAFLORES",
"type": "store",
"isActive": true,
"isDefault": false
}
],
"hasNextPage": false,
"nextCursor": null
}Si hasNextPage es true, pide la siguiente página con nextCursor. Ver
Paginación.
Lee el stock por ubicación
POST / devuelve un nivel por variante y ubicación.
locationIds es opcional: sin él, recibes todas las ubicaciones con nivel.
Este ejemplo lee el SKU POL-001-AZ-M del Polo de algodón.
curl -X POST https://api.ecomiq.pe/api/v1/inventory/levels \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"variantIds": ["7204558912004325377"],
"locationIds": ["7204558912004325378", "7204558912004325380"]
}'[
{
"id": "7204558912004325390",
"variantId": "7204558912004325377",
"locationId": "7204558912004325378",
"onHand": 12,
"committed": 2,
"unavailable": 0,
"available": 10,
"incoming": 0,
"version": 4
},
{
"id": "7204558912004325391",
"variantId": "7204558912004325377",
"locationId": "7204558912004325380",
"onHand": 3,
"committed": 0,
"unavailable": 0,
"available": 3,
"incoming": 0,
"version": 2
}
]Si una ubicación no aparece, la variante aún no tiene nivel ahí.
| Campo | Qué cuenta |
|---|---|
onHand | Unidades físicas en la ubicación. |
committed | Unidades reservadas, por ejemplo para pedidos. |
unavailable | Unidades que no se pueden vender. |
available | Lo que se puede vender: onHand - committed - unavailable. |
incoming | Unidades por recibir. No cuentan en onHand hasta que llegan. |
version | Versión del nivel. Sube con cada cambio. |
Ajusta un nivel
Envía la variante, la ubicación y la version leída en expectedVersion. Si
la variante no tiene nivel en esa ubicación, envía 0: la API crea el nivel.
mode: "adjust"sumaquantityal valor actual. Usa un número negativo para restar.mode: "set"fija el valor.quantityno puede ser negativa.target: "onHand"cambia las unidades físicas.target: "available"cambia lo vendible. La API calculaonHandsumandocommittedyunavailable.
Este ejemplo registra el ingreso de cinco unidades en Almacén Lima.
curl -X POST https://api.ecomiq.pe/api/v1/inventory/adjust \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"variantId": "7204558912004325377",
"locationId": "7204558912004325378",
"target": "onHand",
"mode": "adjust",
"quantity": 5,
"reason": "restock",
"note": "Ingreso de cinco unidades.",
"expectedVersion": 4
}'{
"id": "7204558912004325390",
"variantId": "7204558912004325377",
"locationId": "7204558912004325378",
"onHand": 17,
"committed": 2,
"unavailable": 0,
"available": 15,
"incoming": 0,
"version": 5,
"createdOn": "2026-09-12T14:05:00Z",
"updatedOn": "2026-10-04T15:30:00Z"
}La respuesta trae la nueva version.
reason queda en el
historial de movimientos.
Valores y campos en
Establecer o ajustar existencias.
Resuelve un conflicto
Cada cambio sube la version, también la reserva de un pedido. Con una versión
vieja, la API responde 409 Inventory.Changed y no aplica nada.
En este ejemplo leíste la versión 2 de Tienda Miraflores. Luego un pedido
reservó una unidad y la versión pasó a 3. Tu ajuste con 2 recibe:
{
"title": "Conflict",
"status": 409,
"detail": "Inventory changed. Reload and retry.",
"code": "Inventory.Changed"
}Lee el nivel otra vez y reintenta con la nueva version:
# 1. Lee la version actual: ahora es 3
curl -X POST https://api.ecomiq.pe/api/v1/inventory/levels \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"variantIds": ["7204558912004325377"],
"locationIds": ["7204558912004325380"]
}'
# 2. Reintenta con esa version
curl -X POST https://api.ecomiq.pe/api/v1/inventory/adjust \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"variantId": "7204558912004325377",
"locationId": "7204558912004325380",
"target": "onHand",
"mode": "adjust",
"quantity": -1,
"reason": "damage",
"note": "Una unidad con falla.",
"expectedVersion": 3
}'{
"locationId": "7204558912004325380",
"onHand": 2,
"committed": 1,
"available": 1,
"version": 4
}Con set, confirma que el valor sigue siendo correcto antes de reintentar. Si
una petición se quedó sin respuesta, lee el nivel antes de repetir un adjust:
el primer intento pudo aplicarse.
Actualiza en lote
POST / recibe hasta 100 filas. Cada fila lleva
variante, ubicación, mode, quantity y expectedVersion. reason y note
valen para todo el lote.
Este conteo fija tres niveles. La última fila es POL-001-AZ-L, sin nivel en
Tienda Miraflores: por eso envía expectedVersion: 0.
{
"items": [
{
"variantId": "7204558912004325377",
"locationId": "7204558912004325378",
"mode": "set",
"quantity": 16,
"expectedVersion": 5
},
{
"variantId": "7204558912004325377",
"locationId": "7204558912004325380",
"mode": "set",
"quantity": 3,
"expectedVersion": 4
},
{
"variantId": "7204558912004325379",
"locationId": "7204558912004325380",
"mode": "set",
"quantity": 6,
"expectedVersion": 0
}
],
"reason": "inventoryCount",
"note": "Conteo de fin de mes."
}curl -X POST https://api.ecomiq.pe/api/v1/inventory/batch \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
--data @conteo.json{
"results": [
{
"index": 0,
"id": "7204558912004325377",
"status": "updated",
"errors": []
},
{
"index": 1,
"id": "7204558912004325377",
"status": "updated",
"errors": []
},
{
"index": 2,
"id": "7204558912004325379",
"status": "failed",
"errors": [
{
"field": null,
"code": "Inventory.Changed",
"message": "Inventory changed. Reload and retry."
}
]
}
]
}La API responde 200 con el resultado de cada fila en results[], en el orden
recibido. Cada fila se aplica por separado: una con versión vieja o ubicación
inactiva falla y las demás siguen. Vuelve a leer los niveles de las filas
failed y reenvíalas con su version nueva.
- Para más de 100 filas, sube un archivo a
POST /o divide en lotes de hasta 100.v1/ inventory/ imports - Envía
Idempotency-Keyy repite la misma clave al reintentar el mismo lote. - Para sincronizar desde tu ERP, usa
set: repetir una filasetno suma unidades dos veces.
El lote siempre cambia onHand
Las filas no aceptan target. Para fijar available, usa
POST /.
Errores
| Estado | code | Qué hacer |
|---|---|---|
400 | Inventory.OnHandUnderflow, Inventory.AvailableUnderflow | El cambio deja el stock en negativo: revisa quantity. |
404 | Locations.NotFound | La ubicación no existe o está inactiva. |
409 | Inventory.Changed | Lee el nivel otra vez y reintenta con la nueva version. |
409 | Inventory.NotTracked | La variante no controla stock: activa trackInventory. |
Siguientes pasos
- Integración con ERP: token, productos y pedidos desde tu sistema.
- Eventos: recibe
catalog.stock.changedcon cada cambio de stock. - Inventario: todas las operaciones de stock.
Última actualización: