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?

  1. Ingresar a Wivo — Accede a tu cuenta con tu usuario administrador.
  2. Ir a Integraciones — En el menú de la esquina superior derecha, haz clic en “Integración con API”.
  3. Solicitar habilitación — Haz clic en “Solicitar habilitar API” y agenda una reunión con el equipo de Wivo para activar tu acceso.
  4. Obtener tu clave — Una vez habilitada, se mostrará tu API Key. Es personal, secreta y está asociada a tu cuenta.

Endpoints

MétodoEndpointDescripción
POST/costs/fill/csvCargar 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

CampoTipoObligatorioDescripción
fileFileArchivo CSV (máximo 10 MB)
tenantStringIdentificador de tu cuenta en Wivo
emailStringEmail para recibir el resultado
usernameStringNombre del usuario o sistema
userIdStringIdentificador del usuario en Wivo
clientStringNoIdentificador del cliente
tenantNameStringNoNombre de tu empresa
fromDateStringNoFecha 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ódigoSignificadoReintentar
202Archivo recibido, procesamiento iniciadoNo
400Archivo no es CSV o datos inválidosNo
401API Key ausente o inválidaNo
409Ya hay una carga en curso
429Límite de solicitudes excedido
500Error interno

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

PropiedadValor
CodificaciónUTF-8 (con o sin BOM)
SeparadorComa (,)
Delimitador de textoComillas dobles (")
Fin de líneaCRLF o LF
Primera filaHeaders
Tamaño máximo10 MB

Columnas obligatorias

Si falta alguna de estas columnas, el archivo completo será rechazado:

ColumnaTipoDescripciónEjemplo
ID CuentaTextoIdentificador de la cuenta en el marketplace123456
ID ProductoTextoIdentificador del producto en el marketplaceMLB001
Costo de producto (con IVA)NuméricoCosto unitario con IVA1500.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 Product equivale a ID Producto
  • Costo equivale a Costo de producto (con IVA)

Formato del campo costo

EscribesSe interpreta como
15001500.00
1500.501500.50
1500,501500.50
1.500,501500.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ámetroValor
Máximo de reintentos5
Espera inicial30 segundos
Multiplicador
Espera máxima10 minutos
IntentoEspera
130 segundos
21 minuto
32 minutos
44 minutos
58 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

ProductosTiempo aproximado
~500Segundos
~2,0001 a 2 minutos
~10,0005 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 Producto o ID Cuenta se 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.