Sincronización de productos
Los endpoints de productos permiten mantener el catálogo de Pickwise sincronizado con tu ERP, marketplace o sistema de inventario. Todos los endpoints de escritura son upsert por externalId.
Conceptos clave
Sección titulada «Conceptos clave»externalId — tu identificador canónico
Sección titulada «externalId — tu identificador canónico»Es el ID del producto en tu sistema. Es el único identificador con el que Pickwise dedupica. Debe ser:
- Estable: una vez asignado a un producto, no cambia nunca.
- Inmutable: si cambiás el
externalId, para Pickwise es un producto nuevo. - Único por producto en tu catálogo.
sku — único globalmente
Sección titulada «sku — único globalmente»El sku también es único dentro del tenant: un mismo SKU no puede estar asociado a dos externalId distintos. Si lo intentás, recibís 409 DUPLICATE_SKU.
Campos requeridos vs opcionales
Sección titulada «Campos requeridos vs opcionales»Los únicos campos obligatorios al crear o actualizar un producto son externalId, sku y name. El campo stock es opcional: si no lo enviás al crear, se inicializa en 0; si no lo enviás al actualizar, se mantiene el valor anterior.
Upsert individual
Sección titulada «Upsert individual»POST /products — crea o actualiza un producto por externalId.
Permisos: products:write
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
externalId | string (max 100) | Sí | ID único en tu sistema |
sku | string (max 50) | Sí | Único globalmente |
name | string (max 255) | Sí | Nombre del producto |
stock | number (≥ 0) | No | Default 0 al crear; inmutable en update si se omite |
locationCode | string (max 50) | No | Ubicación en el almacén (ej: A-03-02-01) |
price | number (≥ 0) | No | Precio unitario |
weight | number (> 0) | No | Peso en kg |
length | number (> 0) | No | Largo en cm |
width | number (> 0) | No | Ancho en cm |
height | number (> 0) | No | Alto en cm |
barcode | string (max 50) | No | Código de barras |
category | string (max 255) | No | Categoría |
supplier | string (max 255) | No | Proveedor |
brand | string (max 255) | No | Marca |
isActive | boolean | No | Default true |
isCombo | boolean | No | Default false |
imageUrl | string (max 255) | No | URL de imagen |
metadata | object | No | Campos custom clave-valor |
Ejemplos
Sección titulada «Ejemplos»curl -X POST https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products \ -H "Authorization: Bearer pk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{ "externalId": "ERP-PROD-00123", "sku": "AURICULAR-BT500", "name": "Auricular Bluetooth BT500", "stock": 150, "locationCode": "A-03-02-01", "price": 45.99, "brand": "SoundMax", "metadata": { "color": "Negro", "garantia_meses": 12 } }'const res = await fetch( 'https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ externalId: 'ERP-PROD-00123', sku: 'AURICULAR-BT500', name: 'Auricular Bluetooth BT500', stock: 150, locationCode: 'A-03-02-01', price: 45.99, brand: 'SoundMax', metadata: { color: 'Negro', garantia_meses: 12 } }) });const body = await res.json();console.log(body.action); // "created" o "updated"<?php$payload = json_encode([ 'externalId' => 'ERP-PROD-00123', 'sku' => 'AURICULAR-BT500', 'name' => 'Auricular Bluetooth BT500', 'stock' => 150, 'locationCode' => 'A-03-02-01', 'price' => 45.99, 'brand' => 'SoundMax', 'metadata' => ['color' => 'Negro', 'garantia_meses' => 12]]);
$ch = curl_init('https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . getenv('PICKWISE_API_KEY'), 'Content-Type: application/json']);$response = curl_exec($ch);curl_close($ch);Respuesta exitosa
Sección titulada «Respuesta exitosa»{ "success": true, "action": "created", "data": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "externalId": "ERP-PROD-00123", "sku": "AURICULAR-BT500" }, "requestId": "550e8400-e29b-41d4-a716-446655440000"}action:"created"si elexternalIdno existía,"updated"si ya existía.data.id: UUID interno de Pickwise (no lo uses como referencia desde tu sistema — usá elexternalId).
Upsert en batch
Sección titulada «Upsert en batch»POST /products/batch — hasta 200 productos por request, máximo 5 MB.
Permisos: products:write
El batch es parcial: los productos válidos se procesan aunque otros fallen. Revisá errors y warnings en la respuesta.
curl -X POST https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/batch \ -H "Authorization: Bearer pk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{ "products": [ { "externalId": "ERP-PROD-001", "sku": "SKU-001", "name": "Producto 1", "stock": 100 }, { "externalId": "ERP-PROD-002", "sku": "SKU-002", "name": "Producto 2", "stock": 50 } ] }'// Sincronización completa dividida en chunks de 200function chunk(arr, size) { const out = []; for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); return out;}
for (const batch of chunk(myCatalog, 200)) { const res = await fetch( 'https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/batch', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ products: batch }) } ); const body = await res.json(); if (body.errors?.length) { console.warn(`${body.errors.length} errores en batch`, body.errors); }}<?php$batches = array_chunk($myCatalog, 200);foreach ($batches as $batch) { $ch = curl_init('https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/batch'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['products' => $batch])); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . getenv('PICKWISE_API_KEY'), 'Content-Type: application/json' ]); $response = curl_exec($ch); $body = json_decode($response, true); if (!empty($body['errors'])) { error_log('Errores en batch: ' . count($body['errors'])); }}Respuesta
Sección titulada «Respuesta»{ "success": true, "data": { "successCount": 198, "createdCount": 150, "updatedCount": 48, "errorCount": 2 }, "errors": [ { "index": 45, "externalId": "ERP-PROD-ERR1", "error": "SKU 'SKU-DUPLICADO' ya existe con un externalId diferente" } ], "warnings": [ { "index": 3, "externalId": "ERP-PROD-003", "warning": "locationCode 'Z-99-01' no existe en el warehouse" } ], "requestId": "..."}Consultar productos
Sección titulada «Consultar productos»Por externalId
Sección titulada «Por externalId»GET /products/{externalId} — permisos: products:read
curl https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/ERP-PROD-00123 \ -H "Authorization: Bearer pk_live_xxxx"const res = await fetch( 'https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/ERP-PROD-00123', { headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}` } });const body = await res.json();<?php$ch = curl_init('https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products/ERP-PROD-00123');curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . getenv('PICKWISE_API_KEY')]);$response = curl_exec($ch);Listado con paginación cursor-based
Sección titulada «Listado con paginación cursor-based»GET /products — devuelve páginas de hasta 200 items, con nextCursor para la siguiente página.
Query parameters
Sección titulada «Query parameters»| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
limit | integer | 50 | Max 200 |
cursor | string | - | Token de nextCursor de la respuesta anterior |
updated_since | ISO 8601 | - | Solo productos actualizados desde esa fecha |
active_only | boolean | true | Excluir productos desactivados |
Respuesta
Sección titulada «Respuesta»{ "success": true, "data": [ { "id": "...", "externalId": "ERP-PROD-001", "sku": "SKU-001", "name": "Producto 1", "stock": 100, "isActive": true, "createdAt": "2026-02-10T10:00:00Z", "updatedAt": "2026-03-07T14:30:00Z" } ], "pagination": { "limit": 50, "hasMore": true, "nextCursor": "eyJpZCI6IjEyMzQ1Njc4LTkwYWItY2RlZiJ9", "total": 1523 }, "requestId": "..."}Paginar todo el catálogo
Sección titulada «Paginar todo el catálogo»async function* allProducts() { let cursor = null; while (true) { const params = new URLSearchParams({ limit: '200' }); if (cursor) params.set('cursor', cursor);
const res = await fetch( `https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products?${params}`, { headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}` } } ); const body = await res.json();
for (const product of body.data) yield product;
if (!body.pagination.hasMore) return; cursor = body.pagination.nextCursor; }}
for await (const product of allProducts()) { process(product);}<?phpfunction allProductsPaginated(): Generator { $cursor = null; do { $params = ['limit' => 200]; if ($cursor) $params['cursor'] = $cursor; $url = 'https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products?' . http_build_query($params);
$ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . getenv('PICKWISE_API_KEY') ]); $response = curl_exec($ch); curl_close($ch); $body = json_decode($response, true);
foreach ($body['data'] as $product) yield $product;
$cursor = $body['pagination']['nextCursor'] ?? null; } while ($body['pagination']['hasMore']);}Delta sync con updated_since
Sección titulada «Delta sync con updated_since»En lugar de bajar todo el catálogo periódicamente, traé solo lo que cambió desde la última sync:
GET /products?updated_since=2026-03-07T00:00:00Z&limit=200Desactivar productos
Sección titulada «Desactivar productos»No hay endpoint DELETE. Para “eliminar” un producto del catálogo activo, marcalo como inactivo:
curl -X POST https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products \ -H "Authorization: Bearer pk_live_xxxx" \ -H "Content-Type: application/json" \ -d '{ "externalId": "ERP-PROD-00123", "sku": "AURICULAR-BT500", "name": "Auricular Bluetooth BT500", "stock": 0, "isActive": false }'await fetch('https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ externalId: 'ERP-PROD-00123', sku: 'AURICULAR-BT500', name: 'Auricular Bluetooth BT500', stock: 0, isActive: false })});Reglas y validaciones
Sección titulada «Reglas y validaciones»externalIdinmutable. Si cambiás elexternalIdde un producto, para Pickwise es un producto nuevo.skuúnico: no podés tener dos productos con el mismoskuy distintoexternalId. Recibís409 DUPLICATE_SKU.locationCodetolerante: si el código no existe en el warehouse, el producto se crea igual y recibís un warning.- Campos inmutables linkeados: si un producto está asociado a órdenes activas, no podés cambiar el
skusin primero resolver esas órdenes.
Errores comunes
Sección titulada «Errores comunes»| Código | Caso típico | Acción |
|---|---|---|
400 VALIDATION_ERROR | stock negativo, externalId vacío | Revisar details |
409 DUPLICATE_SKU | Dos externalId quieren el mismo SKU | Unificar en tu sistema |
413 PAYLOAD_TOO_LARGE | Batch > 5 MB o > 200 items | Partir en chunks más chicos |
Ver guía de manejo de errores para la lista completa.