Guía

Inventario sin dos escritores

Moku separa stock físico y reservas para calcular disponibilidad y proteger unidades comprometidas.

on_handStock físico

La cantidad que modifica el sistema con autoridad.

reservedReservado

Unidades retenidas por checkout, pedido o reserva pública.

availableDisponible

on_hand - reserved. Publica este valor en canales externos.

Una ubicación predeterminada por vendedor

Cada item devuelve location_id: default, una ubicación contable explícita dentro de vendor_id. El mismo nombre en dos vendedores no representa stock compartido. Los IDs actuales de inventario no cambian: guárdalos como valores opacos, sin recalcularlos.

Todavía no existen administración de bodegas ni asignación entre ubicaciones. Un futuro inventario administrado por un marketplace usará otra ubicación para no descontar dos veces el stock del vendedor.

Reservas acotadas

Cada item admite hasta 256 reservas activas. Al alcanzar el límite, una reserva nueva falla con RESERVATION_CONFLICT; renovar o liquidar una reserva vigente sigue permitido. Las reservas vencidas liberan disponibilidad y capacidad inmediatamente, aunque la limpieza llegue después.

El catálogo no reemplaza el inventario autorizado: si falta un registro de stock o está mal formado, la operación falla de forma cerrada. No se toma stock de una instantánea del producto.

Cada item también declara stock_pool_id: default. El ID opaco existente no cambia; futuros pools serán aditivos.

Seguir la guía de producto a stock

Ajustar stock

La API usa producción, aunque los pagos estén en sandbox. Los IDs y las respuestas de esta guía son ficticios; reemplázalos por los de tu tienda.

Ajustar stock físico de forma idempotente

Solicitud · ejemplo ilustrativo

cURL
: "${MOKU_PAT:?}" &&
test "${MOKU_ALLOW_WRITES:-}" = "1" &&
curl --silent --show-error --fail-with-body \
  --request POST \
  'https://moku.cl/api/v1/vendors/vendor_demo_ceramica/inventory-items/WyJwcm9kdWN0X2RlbW9fdGF6b24iLCJ2YXJpYXRpb25fZGVtb19henVsIl0/adjustments' \
  --header "Authorization: Bearer ${MOKU_PAT}" \
  --header 'Accept: application/json' \
  --header 'Idempotency-Key: demo_stock_001' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "target_on_hand": 8,
  "expected_version": 7
}'
PHP

Ejemplo para PHP CLI con la extensión cURL. No es un plugin de WordPress ni debe ejecutarse en el navegador.

<?php
$token = getenv('MOKU_PAT');
if ($token === false || $token === '') {
    throw new RuntimeException('MOKU_PAT');
}
if (getenv('MOKU_ALLOW_WRITES') !== '1') {
    throw new RuntimeException('MOKU_ALLOW_WRITES=1');
}

$url = 'https://moku.cl/api/v1/vendors/vendor_demo_ceramica/inventory-items/WyJwcm9kdWN0X2RlbW9fdGF6b24iLCJ2YXJpYXRpb25fZGVtb19henVsIl0/adjustments';
$body = <<<'MOKU_REQUEST_JSON'
{
  "target_on_hand": 8,
  "expected_version": 7
}
MOKU_REQUEST_JSON;
$curl = curl_init($url);
if ($curl === false) {
    throw new RuntimeException('curl_init');
}
curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Authorization: Bearer ' . $token,
        'Idempotency-Key: demo_stock_001',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($curl);
if ($response === false) {
    $message = curl_error($curl);
    curl_close($curl);
    throw new RuntimeException($message);
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$result = json_decode($response, false, 512, JSON_THROW_ON_ERROR);
$failed = $status < 200 || $status >= 300;
if ($failed) {
    fwrite(STDERR, "HTTP {$status}\n");
}
fwrite(
    $failed ? STDERR : STDOUT,
    json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL
);
exit($failed ? 1 : 0);

Respuesta 200 application/json

{
  "data": {
    "id": "WyJwcm9kdWN0X2RlbW9fdGF6b24iLCJ2YXJpYXRpb25fZGVtb19henVsIl0",
    "vendor_id": "vendor_demo_ceramica",
    "location_id": "default",
    "stock_pool_id": "default",
    "product_id": "product_demo_tazon",
    "product_name": "Tazón de cerámica",
    "product_image_url": "https://example.com/tazon-azul.jpg",
    "variation_id": "variation_demo_azul",
    "variation_options": {
      "Color": "Azul"
    },
    "sku": "TAZ-AZUL",
    "on_hand": 8,
    "reserved": 2,
    "available": 6,
    "version": 8,
    "status": "low_stock"
  }
}

Omite authority_connection_id cuando Moku controla el Stock Pool. Cuando WooCommerce tiene rol inventory: source, envía el ID exacto de esa conexión.

Los replays exactos se reconocen durante al menos 30 días y la identidad incluye token y operación. Nunca reutilices intencionalmente una clave para otro trabajo; una credencial nueva no comparte los recibos de la anterior.

Reservas públicas

Una conexión con rol de destino y stock Moku puede reservar entre 60 y 3600 segundos, renovar, consumir o liberar. Una reserva creada por un pedido externo se administra exclusivamente mediante el estado del pedido.

Conflictos

  1. Ante INVENTORY_VERSION_CONFLICT, vuelve a leer el item.
  2. Recalcula target_on_hand sobre la versión actual.
  3. Reintenta con una clave nueva.

ON_HAND_BELOW_RESERVED impide establecer stock por debajo de compromisos activos. AUTHORITY_CONFLICT indica que otro sistema es el escritor vigente.