idIdentidad estableGuarda IDs de producto y variación; no los derives del slug o SKU.
Guía
El catálogo usa identidades opacas y revisiones optimistas. El stock nunca viaja en el recurso producto.
idIdentidad estableGuarda IDs de producto y variación; no los derives del slug o SKU.
revisionConcurrenciaEnvía expected_revision al editar o cambiar estado.
statusCiclo de vidadraft, published y archived.
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.
Solicitud · ejemplo ilustrativo
: "${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/products' \
--header "Authorization: Bearer ${MOKU_PAT}" \
--header 'Accept: application/json' \
--header 'Idempotency-Key: demo_product_001' \
--header 'Content-Type: application/json' \
--data-raw '{
"kind": "variable",
"name": "Tazón nuevo de cerámica",
"slug": "tazon-nuevo-de-ceramica",
"description": "Tazón de cerámica hecho a mano.",
"price": 15000,
"original_price": null,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-NUEVO-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"catalog_authority_connection_id": "con_demo_woocommerce",
"base_price_authority_connection_id": "con_demo_woocommerce"
}'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/products';
$body = <<<'MOKU_REQUEST_JSON'
{
"kind": "variable",
"name": "Tazón nuevo de cerámica",
"slug": "tazon-nuevo-de-ceramica",
"description": "Tazón de cerámica hecho a mano.",
"price": 15000,
"original_price": null,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-NUEVO-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"catalog_authority_connection_id": "con_demo_woocommerce",
"base_price_authority_connection_id": "con_demo_woocommerce"
}
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_product_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 201 application/json
{
"data": {
"id": "product_demo_tazon_nuevo",
"vendor_id": "vendor_demo_ceramica",
"catalog_status": "active",
"status": "draft",
"revision": 1,
"kind": "variable",
"name": "Tazón nuevo de cerámica",
"slug": "tazon-nuevo-de-ceramica",
"description": "Tazón de cerámica hecho a mano.",
"price": 15000,
"original_price": null,
"discount": 0,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"id": "variation_demo_azul_nueva",
"status": "active",
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-NUEVO-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"created_at": "2026-08-26T09:04:00.000Z",
"updated_at": "2026-08-26T09:04:00.000Z",
"published_at": null,
"archived_at": null
}
}
Incluye por separado catalog_authority_connection_id y base_price_authority_connection_id, usando null cuando Moku controla el dominio. La creación comienza como borrador.
Aunque la ruta usa PATCH, no acepta un parche parcial con solo el campo cambiado. Construye un cuerpo completo según ProductUpdateRequest, conserva los valores editables que no quieres cambiar y agrega expected_revision de la última lectura.
No reenvíes a ciegas la respuesta de GET: elimina los campos de solo lectura como id, vendor_id, revision, el status del producto y sus timestamps. Conserva los IDs y estados de las variaciones existentes cuando el esquema los requiera; no cambies kind.
Incluye por separado catalog_authority_connection_id y base_price_authority_connection_id; usa el ID de cada fuente o null cuando Moku controla ese dominio. Omitir campos opcionales puede vaciar o restablecer esos valores; envía los que quieres conservar. Publicar, despublicar, archivar y restaurar usan operaciones separadas.
Este ejemplo es una escritura en producción y requiere autorización. Muestra el cambio desde la revisión 3 a la 4; no lo ejecutes sobre tu catálogo sin reconstruir el cuerpo con sus datos actuales.
Solicitud · ejemplo ilustrativo
: "${MOKU_PAT:?}" &&
test "${MOKU_ALLOW_WRITES:-}" = "1" &&
curl --silent --show-error --fail-with-body \
--request PATCH \
'https://moku.cl/api/v1/vendors/vendor_demo_ceramica/products/product_demo_tazon' \
--header "Authorization: Bearer ${MOKU_PAT}" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
"kind": "variable",
"name": "Tazón de cerámica",
"slug": "tazon-de-ceramica",
"description": "Tazón de cerámica hecho a mano, esmaltado en azul.",
"price": 15000,
"original_price": null,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"id": "variation_demo_azul",
"status": "active",
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"expected_revision": 3,
"catalog_authority_connection_id": "con_demo_woocommerce",
"base_price_authority_connection_id": "con_demo_woocommerce"
}'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/products/product_demo_tazon';
$body = <<<'MOKU_REQUEST_JSON'
{
"kind": "variable",
"name": "Tazón de cerámica",
"slug": "tazon-de-ceramica",
"description": "Tazón de cerámica hecho a mano, esmaltado en azul.",
"price": 15000,
"original_price": null,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"id": "variation_demo_azul",
"status": "active",
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"expected_revision": 3,
"catalog_authority_connection_id": "con_demo_woocommerce",
"base_price_authority_connection_id": "con_demo_woocommerce"
}
MOKU_REQUEST_JSON;
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException('curl_init');
}
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Authorization: Bearer ' . $token,
'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": "product_demo_tazon",
"vendor_id": "vendor_demo_ceramica",
"catalog_status": "active",
"status": "published",
"revision": 4,
"kind": "variable",
"name": "Tazón de cerámica",
"slug": "tazon-de-ceramica",
"description": "Tazón de cerámica hecho a mano, esmaltado en azul.",
"price": 15000,
"original_price": null,
"discount": 0,
"category": "hogar",
"subcategory": "ceramica",
"image_urls": [
"https://example.com/tazon-azul.jpg"
],
"badge": null,
"options": {
"Color": [
"Azul"
]
},
"default_options": {
"Color": "Azul"
},
"variations": [
{
"id": "variation_demo_azul",
"status": "active",
"options": {
"Color": "Azul"
},
"price": 15000,
"original_price": null,
"sku": "TAZ-AZUL"
}
],
"features": [
"Hecho a mano"
],
"related_product_ids": [],
"tags": [
"ceramica"
],
"sku": null,
"weight_kg": 0.4,
"source_region": "metropolitana",
"created_at": "2026-08-01T12:00:00.000Z",
"updated_at": "2026-08-26T09:03:00.000Z",
"published_at": "2026-08-01T12:00:00.000Z",
"archived_at": null
}
}
Ante PRODUCT_REVISION_CONFLICT, vuelve a leer, compara y reconstruye el cuerpo editable con la revisión vigente. No descartes cambios ajenos ni sustituyas solo el número de revisión.