Manejo de errores
Toda la API devuelve errores con un formato consistente para que tu cliente pueda manejarlos de forma uniforme.
Formato estándar de error
Sección titulada «Formato estándar de error»{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Los datos enviados no son válidos.", "details": [ { "field": "items[0].quantity", "message": "quantity debe ser mayor a 0", "value": -1 } ] }, "requestId": "550e8400-e29b-41d4-a716-446655440000"}| Campo | Descripción |
|---|---|
success | Siempre false en respuestas de error. |
error.code | Código machine-readable. Usalo para branching en tu cliente. |
error.message | Mensaje human-readable en español. Apto para logs; no siempre apto para mostrar al usuario final. |
error.details | Array de errores de validación por campo (solo en 400 VALIDATION_ERROR). |
requestId | UUID único de la request. Incluilo siempre cuando reportes un problema a soporte. |
Códigos de error
Sección titulada «Códigos de error»| HTTP | Código | Descripción | Acción recomendada |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Datos inválidos en el request | Corregir según details |
| 400 | INVALID_JSON | Body mal formado | Verificar serialización |
| 401 | UNAUTHORIZED | API Key inválida, expirada o ausente | Revisar header Authorization |
| 403 | FORBIDDEN | Key válida sin permiso para el recurso | Pedir ajuste de permisos a soporte |
| 403 | IP_NOT_ALLOWED | IP no autorizada para esta key | Actualizar allowlist con soporte |
| 404 | NOT_FOUND | Recurso no encontrado | Verificar externalId |
| 409 | CONFLICT | Conflicto de estado (ej: cancelar orden en proceso) | Revisar estado actual |
| 409 | DUPLICATE_SKU | SKU existente con otro externalId | Unificar en un solo externalId |
| 409 | IDEMPOTENCY_CONFLICT | Misma idempotency key, distintos parámetros | Generar nueva key o mandar mismos parámetros |
| 413 | PAYLOAD_TOO_LARGE | Body excede límite (5MB productos, 10MB órdenes) | Reducir tamaño del batch |
| 429 | RATE_LIMIT_EXCEEDED | Superado el rate limit | Esperar retryAfter segundos |
| 500 | INTERNAL_ERROR | Error interno del servidor | Retry con backoff; si persiste, reportar a soporte |
Errores por código HTTP
Sección titulada «Errores por código HTTP»400 — Validación
Sección titulada «400 — Validación»El request no cumple el schema o tiene valores fuera de rango. El campo details te dice exactamente qué falla:
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Los datos enviados no son válidos.", "details": [ { "field": "items[0].quantity", "message": "quantity debe ser mayor a 0", "value": -1 }, { "field": "customer.email", "message": "email debe tener formato válido", "value": "no-es-email" } ] }, "requestId": "..."}No reintentar. Arreglá el request antes de volver a mandarlo.
401 / 403 — Autenticación
Sección titulada «401 / 403 — Autenticación»Ver guía de autenticación para el detalle. Resumen:
- 401: problema de key (ausente, mal formateada, revocada, expirada).
- 403: key válida pero sin permiso, o IP bloqueada.
No reintentar: es un problema de configuración, no transitorio.
404 — No existe
Sección titulada «404 — No existe»El recurso (producto u orden) con ese externalId no existe en Pickwise. Puede ser un typo o que todavía no lo hayas creado.
409 — Conflicto
Sección titulada «409 — Conflicto»Depende del código específico:
CONFLICTgenérico: típicamente al cancelar una orden que ya está enIN_PICKINGo posterior. No reintentar; consultar estado antes.DUPLICATE_SKU: el SKU que querés usar está asociado a otroexternalId. Decidí cuál es el correcto en tu sistema y unificá.IDEMPOTENCY_CONFLICT: ver guía de idempotencia.
413 — Payload demasiado grande
Sección titulada «413 — Payload demasiado grande»/products/batch: máximo 5 MB y 200 items./orders/batch: máximo 10 MB y 50 items.
Partí el batch en chunks más chicos.
429 — Rate limit
Sección titulada «429 — Rate limit»Ver guía de rate limiting. Respetá retryAfter y reintentá.
5xx — Error del servidor
Sección titulada «5xx — Error del servidor»Algo falló del lado de Pickwise. Siempre es retriable con backoff exponencial. Si persiste después de 3 intentos, reportá a soporte con el requestId.
Estrategia de retry por tipo
Sección titulada «Estrategia de retry por tipo»| Código | ¿Retriable? | Estrategia |
|---|---|---|
400 VALIDATION_ERROR | ❌ | Corregir request. |
400 INVALID_JSON | ❌ | Corregir serialización. |
| 401 / 403 | ❌ | Problema de configuración. |
| 404 | ❌ | Verificar externalId. |
409 CONFLICT | ❌ | Revisar estado. |
409 DUPLICATE_SKU | ❌ | Unificar externalId. |
409 IDEMPOTENCY_CONFLICT | ❌ | Generar nueva idempotency key. |
| 413 | ❌ | Reducir tamaño del batch. |
| 429 | ✅ | Esperar retryAfter y reintentar. |
500 INTERNAL_ERROR | ✅ | Backoff exponencial, max 3 intentos. |
| 502 / 503 / 504 | ✅ | Backoff exponencial, max 3 intentos. |
El campo requestId
Sección titulada «El campo requestId»Todo response (éxito o error) incluye un requestId único. Guardarlo en tus logs te permite:
- Cruzar errores del lado cliente con logs del lado Pickwise.
- Cuando reportás un problema a soporte, incluirlo para que localicen la request exacta en cuestión de segundos.
Ejemplo end-to-end
Sección titulada «Ejemplo end-to-end»Cliente Node.js con handling completo de errores:
class PickwiseError extends Error { constructor(code, message, status, details, requestId) { super(message); this.code = code; this.status = status; this.details = details; this.requestId = requestId; }}
const NON_RETRIABLE = new Set([ 'VALIDATION_ERROR', 'INVALID_JSON', 'UNAUTHORIZED', 'FORBIDDEN', 'IP_NOT_ALLOWED', 'NOT_FOUND', 'CONFLICT', 'DUPLICATE_SKU', 'IDEMPOTENCY_CONFLICT', 'PAYLOAD_TOO_LARGE']);
async function apiCall(url, init, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const res = await fetch(url, init); const body = await res.json().catch(() => ({}));
if (res.ok) return body;
const code = body?.error?.code ?? 'UNKNOWN'; const message = body?.error?.message ?? res.statusText; const details = body?.error?.details; const requestId = body?.requestId;
// No retriable — tirar error inmediatamente if (NON_RETRIABLE.has(code)) { throw new PickwiseError(code, message, res.status, details, requestId); }
// 429: respetar retryAfter if (res.status === 429) { const retryAfter = body?.error?.retryAfter ?? Math.pow(2, attempt); await new Promise(r => setTimeout(r, retryAfter * 1000)); continue; }
// 5xx: backoff exponencial if (res.status >= 500) { await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000)); continue; }
// Otros 4xx no listados: tirar error throw new PickwiseError(code, message, res.status, details, requestId); }
throw new Error('Max retries exceeded');}
// Usotry { const order = await apiCall( 'https://api-{CLIENTE}.pickwise.com.ar/api/v1/public/orders', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.PICKWISE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify(orderPayload) } ); console.log('Order created:', order.data.externalId);} catch (err) { if (err instanceof PickwiseError) { console.error(`[${err.requestId}] ${err.code}: ${err.message}`); if (err.details) console.error('Validation:', err.details); } else { console.error('Unexpected error:', err); }}