Carga de costos de productos por API
Wivo permite actualizar los costos de tus productos de forma programática mediante un archivo CSV enviado a la API. Los costos se procesan en segundo plano y recibirás una notificación por email cuando el proceso termine.
Esta funcionalidad está disponible para cuentas con plan Pro.
¿Para qué sirve?
- Sincronizar costos automáticamente desde tu ERP, data warehouse o sistema contable
- Programar actualizaciones periódicas (diarias, semanales) sin intervención manual
- Actualizar costos de miles de productos en una sola operación
Requisitos
- Cuenta Wivo con plan Pro
- API Key activa (se obtiene desde la sección Integraciones en la plataforma)
Base URL
https://api.wivoanalytics.com
Autenticación
Todas las solicitudes deben incluir la API Key en el siguiente header HTTP:
Ocp-Apim-Subscription-Key: TU_API_KEY_AQUI
Solicitudes sin API Key o con clave inválida reciben 401 Unauthorized.
Importante: Tu API Key es un secreto. No la compartas, no la incluyas en código fuente público ni la expongas en logs. Almacénala en variables de entorno o en un gestor de secretos.
¿Cómo obtener tu API Key?
- Ingresar a Wivo — Accede a tu cuenta con tu usuario administrador.
- Ir a Integraciones — En el menú de la esquina superior derecha, haz clic en “Integración con API”.
- Solicitar habilitación — Haz clic en “Solicitar habilitar API” y agenda una reunión con el equipo de Wivo para activar tu acceso.
- Obtener tu clave — Una vez habilitada, se mostrará tu API Key. Es personal, secreta y está asociada a tu cuenta.
Endpoints
| Método | Endpoint | Descripción |
|---|---|---|
POST | /costs/fill/csv | Cargar archivo CSV con costos |
GET | /costs/fill-status/{tenant} | Consultar estado de una carga en curso |
GET | /costs/fill-history/{tenant} | Consultar historial de cargas (últimos 7 días) |
POST /costs/fill/csv
Content-Type: multipart/form-data
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
file | File | Sí | Archivo CSV (máximo 10 MB) |
tenant | String | Sí | Identificador de tu cuenta en Wivo |
email | String | Sí | Email para recibir el resultado |
username | String | Sí | Nombre del usuario o sistema |
userId | String | Sí | Identificador del usuario en Wivo |
client | String | No | Identificador del cliente |
tenantName | String | No | Nombre de tu empresa |
fromDate | String | No | Fecha desde la cual aplicar costos (YYYY-MM-DD). Si no se envía, se aplican desde hace 2 años |
Ejemplos de código
cURL:
curl -X POST "https://api.wivoanalytics.com/costs/fill/csv" \
-H "Ocp-Apim-Subscription-Key: TU_API_KEY_AQUI" \
-F "file=@costos.csv" \
-F "tenant=abc123" \
-F "email=usuario@miempresa.com" \
-F "username=Pipeline de Costos" \
-F "userId=usr_001"
Python (requests):
import requests
url = "https://api.wivoanalytics.com/costs/fill/csv"
headers = {"Ocp-Apim-Subscription-Key": "TU_API_KEY_AQUI"}
with open("costos.csv", "rb") as f:
files = {"file": ("costos.csv", f, "text/csv")}
data = {
"tenant": "abc123",
"email": "usuario@miempresa.com",
"username": "Pipeline de Costos",
"userId": "usr_001",
}
response = requests.post(url, headers=headers, files=files, data=data)
print(response.status_code) # 202
print(response.json()) # {"processId": "a1b2c3d4-..."}
Node.js (axios):
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");
const form = new FormData();
form.append("file", fs.createReadStream("costos.csv"));
form.append("tenant", "abc123");
form.append("email", "usuario@miempresa.com");
form.append("username", "Pipeline de Costos");
form.append("userId", "usr_001");
const response = await axios.post(
"https://api.wivoanalytics.com/costs/fill/csv",
form,
{ headers: { "Ocp-Apim-Subscription-Key": "TU_API_KEY_AQUI", ...form.getHeaders() } }
);
console.log(response.data); // { processId: "a1b2c3d4-..." }
Respuesta exitosa — 202 Accepted
{ "processId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
Respuestas del servidor
| Código | Significado | Reintentar |
|---|---|---|
202 | Archivo recibido, procesamiento iniciado | No |
400 | Archivo no es CSV o datos inválidos | No |
401 | API Key ausente o inválida | No |
409 | Ya hay una carga en curso | Sí |
429 | Límite de solicitudes excedido | Sí |
500 | Error interno | Sí |
400 Bad Request:
{ "success": false, "error": "Este endpoint solo acepta archivos CSV" }
401 Unauthorized:
{ "success": false, "error": "Authentication required" }
409 Conflict:
{ "message": "En estos momentos estamos procesando tu planilla. Por favor, espere a que finalice antes de subir una nueva planilla." }
500 Internal Server Error:
{ "message": "Error al subir el archivo" }
GET /costs/fill-status/{tenant}
Retorna el estado actual de la carga en curso.
Con proceso activo:
{ "process": { "status": "in_progress", "processId": "a1b2c3d4-...", "startedAt": "2026-03-24T14:30:00Z" } }
Sin proceso activo:
{ "process": null }
Especificación del archivo CSV
| Propiedad | Valor |
|---|---|
| Codificación | UTF-8 (con o sin BOM) |
| Separador | Coma (,) |
| Delimitador de texto | Comillas dobles (") |
| Fin de línea | CRLF o LF |
| Primera fila | Headers |
| Tamaño máximo | 10 MB |
Columnas obligatorias
Si falta alguna de estas columnas, el archivo completo será rechazado:
| Columna | Tipo | Descripción | Ejemplo |
|---|---|---|---|
ID Cuenta | Texto | Identificador de la cuenta en el marketplace | 123456 |
ID Producto | Texto | Identificador del producto en el marketplace | MLB001 |
Costo de producto (con IVA) | Numérico | Costo unitario con IVA | 1500.50 |
Los nombres de columna son sensibles a mayúsculas y acentos. El orden no importa.
Columnas opcionales
Puedes incluir columnas adicionales para enriquecer la información: Marketplace, Cuenta, SKU Producto, Nombre Producto, Producto Interno, SKU Producto Interno, Marca Interna y Categoría Interna. Si no se incluyen, se asignan valores por defecto.
Nombres alternativos (legacy)
Para compatibilidad con planillas antiguas se aceptan:
ID Productequivale aID ProductoCostoequivale aCosto de producto (con IVA)
Formato del campo costo
| Escribes | Se interpreta como |
|---|---|
1500 | 1500.00 |
1500.50 | 1500.50 |
1500,50 | 1500.50 |
1.500,50 | 1500.50 |
Rango válido: -99,999,999.99 a 99,999,999.99. Valores fuera de rango o inválidos se almacenan como vacío.
Ejemplos de archivo CSV
Archivo completo (todas las columnas):
Marketplace,Cuenta,ID Cuenta,SKU Producto,Nombre Producto,ID Producto,Costo de producto (con IVA),Producto Interno,SKU Producto Interno,Marca Interna,Categoría Interna
mercadolibre,Mi Tienda,123456,SKU-001,Camiseta Básica,MLB001,1500.50,Camiseta Genérica,INT-001,Nike,Ropa
mercadolibre,Mi Tienda,123456,SKU-002,"Pantalón Cargo, modelo 2024",MLB002,3200,Pantalón Genérico,INT-002,Adidas,Ropa
mercadolibre,Mi Tienda,123456,SKU-003,Zapatillas Running,MLB003,8500.00,Zapatillas Genéricas,INT-003,Puma,Calzado
Archivo mínimo (solo columnas requeridas):
ID Cuenta,ID Producto,Costo de producto (con IVA)
123456,MLB001,1500.50
123456,MLB002,3200
123456,MLB003,8500.00
Generación del CSV por código
Python (pandas):
import pandas as pd
df = pd.DataFrame({
"ID Cuenta": ["123456", "123456"],
"ID Producto": ["MLB001", "MLB002"],
"Costo de producto (con IVA)": [1500.50, 3200.00],
})
df.to_csv("costos.csv", index=False, encoding="utf-8")
Node.js:
const { stringify } = require("csv-stringify/sync");
const fs = require("fs");
const data = [
["ID Cuenta", "ID Producto", "Costo de producto (con IVA)"],
["123456", "MLB001", "1500.50"],
["123456", "MLB002", "3200.00"],
];
fs.writeFileSync("costos.csv", stringify(data), "utf-8");
Reintentos automáticos
Para los códigos 409, 429 y 500, usa backoff exponencial:
| Parámetro | Valor |
|---|---|
| Máximo de reintentos | 5 |
| Espera inicial | 30 segundos |
| Multiplicador | 2× |
| Espera máxima | 10 minutos |
| Intento | Espera |
|---|---|
| 1 | 30 segundos |
| 2 | 1 minuto |
| 3 | 2 minutos |
| 4 | 4 minutos |
| 5 | 8 minutos |
Para errores 409, consulta GET /costs/fill-status/{tenant} antes de reintentar para verificar si hay un proceso activo.
Ejemplo Python con reintentos:
import time, random, requests
API_KEY = "TU_API_KEY_AQUI"
API_URL = "https://api.wivoanalytics.com/costs/fill/csv"
MAX_RETRIES = 5
def upload_costs(csv_path, form_data):
headers = {"Ocp-Apim-Subscription-Key": API_KEY}
for attempt in range(1, MAX_RETRIES + 1):
with open(csv_path, "rb") as f:
files = {"file": ("costos.csv", f, "text/csv")}
response = requests.post(API_URL, headers=headers, files=files, data=form_data)
if response.status_code == 202:
return response.json()
if response.status_code in (400, 401):
raise Exception(f"Error {response.status_code}: {response.json()}")
if response.status_code in (409, 429, 500):
delay = min(30 * (2 ** (attempt - 1)), 600)
time.sleep(delay + random.uniform(-5, 5))
raise Exception("Se superó el número máximo de reintentos")
Tiempos de procesamiento
| Productos | Tiempo aproximado |
|---|---|
| ~500 | Segundos |
| ~2,000 | 1 a 2 minutos |
| ~10,000 | 5 a 10 minutos |
Reglas de negocio
- Un proceso a la vez — Solo puede haber una carga activa por cuenta. Si envías un archivo mientras otro se está procesando, recibirás un error
409. - Filas incompletas — Las filas sin
ID ProductooID Cuentase ignoran; el resto se procesa normalmente. - Costos inválidos — Valores vacíos, inválidos o fuera de rango se almacenan como vacío (no rechazan el archivo).
- Procesamiento por lotes — Los productos se procesan en grupos de 500.
- Identificación por nombre — Las columnas se identifican por nombre, no por posición.
Preguntas frecuentes
¿Puedo enviar solo las 3 columnas obligatorias? Sí. Las columnas opcionales se completan con valores por defecto.
¿Importa el orden de las columnas? No.
¿El 202 garantiza que los costos se actualizaron?
No, solo confirma que el archivo fue recibido y el procesamiento comenzó. Espera la notificación por email o consulta el estado con GET /costs/fill-status/{tenant}.
¿Qué hace el parámetro fromDate?
Define desde qué fecha se aplican los costos. Si no lo envías, se aplican desde el primer día del mes de hace 2 años.
¿Es la misma API Key que uso para la API de datos de Wivo? Sí.
¿Tienes dudas? Escríbenos a contacto@wivoanalytics.com o accede a la plataforma en app.wivoanalytics.com.
Al contactar soporte, incluye: tenant, processId, código HTTP recibido, y fecha y hora del error.