API de órdenes (beta)

Beta. La API está en beta: el contrato puede cambiar, y avisamos antes.

Si tu ecommerce es de desarrollo propio, no hay un conector que descargue tus órdenes. Con esta API las envías tú, desde tu sistema, y aparecen en Wivo junto a las de tus marketplaces: en Ventas, en Rentabilidad y en el Maestro.

Envías lotes de órdenes a POST /orders. Cada orden viaja completa, con todas sus líneas, y Wivo calcula el resto: el costo desde tu Maestro, y el despacho y la comisión de la pasarela repartidos entre las líneas.

Está incluida en el plan Pro: no es un add-on. Por ahora es solo para cuentas de Chile, con montos en pesos chilenos (CLP).

¿Para qué sirve?

  • Ver las ventas de tu tienda propia en los mismos dashboards que las de tus marketplaces
  • Calcular su rentabilidad con el costo de tu Maestro, el costo de despacho y la comisión de tu pasarela de pago
  • Mantener al día cancelaciones y devoluciones: reenvías la orden con su nuevo estado y Wivo la reemplaza

Requisitos

  • Cuenta Wivo con plan Pro, de Chile
  • Tu API Key, de la vista Integración con API
  • El source_id de tu fuente Tienda propia (API)

No necesitas el add-on de la API de datos: este recurso viene incluido en el plan Pro.

¿Cómo obtener tu API Key?

  1. Ingresa a Wivo con tu usuario.
  2. Abre Integración con API en el menú, o entra directo a app.wivoanalytics.com/u/0/#/api.
  3. Copia tu API Key. Con el plan Pro la obtienes ahí mismo, sin el add-on de la API de datos. Es personal, secreta y está asociada a tu cuenta.

Es la misma clave para todos los recursos de la API de Wivo.

¿Cómo obtener tu source_id?

El source_id identifica tu fuente Tienda propia (API): es la “integración” a la que llegan tus órdenes. La creas tú, sin credenciales:

  1. Abre Integraciones en el menú, o entra a app.wivoanalytics.com/u/0/#/marketplaces/accounts.
  2. Haz clic en Agregar y, en Tiendas Ecommerce, elige Tienda propia (API) · Beta. No te pide credenciales: la fuente se crea al elegirla.
  3. Copia el source_id que aparece en pantalla.

Después lo sigues viendo en Integraciones, junto a tu Tienda propia (API). Cada cuenta tiene una sola: si la eliges de nuevo, Wivo te muestra la que ya tienes.

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.

POST /orders

Recibe un lote de hasta 100 órdenes, en JSON (Content-Type: application/json).

Lo que tienes que saber antes de integrar

  • La unidad es la orden completa. Cada vez que envías una orden, trae todas sus líneas, no solo la que cambió. Reenviar el mismo order_id reemplaza la orden entera: una línea que no viene, desaparece. Por eso reenviar nunca duplica.
  • purchase_date no cambia. Es la fecha de compra y se mantiene igual en cada reenvío. Si la corres más de 5 días, la orden aparece dos veces.
  • Todo o nada. Si una sola orden del lote tiene un error, no entra ninguna. La respuesta 422 te dice qué campo de qué orden falló, para que corrijas y reenvíes el lote completo.
  • 202 quiere decir recibido, no insertado. Tus órdenes aparecen en la app unos minutos después.
  • Montos en CLP, con IVA incluido, tal como los ve el comprador.
  • El costo del producto no viaja en la orden. Se carga en el Maestro, como en cualquier otro canal (ver la receta).

Cuerpo

CampoTipoObligatorioDescripción
source_idUUIDSíEl source_id de tu Tienda propia (API). Tiene que ser de la misma cuenta que la API Key
ordersListaSíEntre 1 y 100 órdenes

Campos de cada orden

CampoTipoObligatorioDescripción
order_idTexto (id)SíID de la orden en tu tienda. Único por fuente y dentro del lote. Reenviarlo reemplaza la orden completa
purchase_dateFechaSíFecha de compra. No cambia entre envíos
last_update_dateFechaSíÚltima modificación de la orden. No puede ser anterior a purchase_date
currencyTextoSíSiempre "CLP"
payment_statusTextoSípaid o unpaid
shipment_statusTextoNoEstado del despacho (ver estados). Default noinformation
shopperObjetoNoEl comprador: id (obligatorio dentro del objeto) y name
shipping_costMontoNoLo que te cuesta a ti despachar la orden, no lo que paga el comprador
shipping_incomeMontoNoLo que pagó el comprador por el despacho, con IVA. Si no viene, es 0
payment_feeMontoNoComisión de la pasarela de pago (Webpay, Mercado Pago, etc.) de la orden
itemsListaSíTodas las líneas de la orden, entre 1 y 200

Campos de cada línea

CampoTipoObligatorioDescripción
product_idTexto (id)SíID del producto en tu tienda. Es el ID Producto con que aparece en el Maestro
skuTexto (id)NoSKU del producto
nameTextoSíNombre del producto
variantObjetoNoLa variante: id (obligatorio dentro del objeto) y name
brandTextoNoMarca
categoryTextoNoCategoría
quantityEnteroSíUnidades, de 1 a 100.000
unit_priceMontoSíPrecio unitario de lista, con IVA, antes del descuento
discountMontoNoDescuento total de la línea (no unitario), con IVA. Default 0. No puede superar unit_price × quantity
statusTextoSíEstado de la línea: paid, canceled, returned o pending

Si no envías variant, brand, category o shopper, Wivo los muestra como “Sin información”.

Estados

Solo se aceptan los estados de Wivo. Si tu tienda usa otros nombres, conviértelos antes de enviar: un estado que no está en la lista rechaza el lote.

items[].status, estado de la línea:

ValorSignificado
paidPagada
canceledCancelada
returnedDevuelta
pendingPendiente de pago. Wivo la cuenta como no concretada, igual que una cancelada, hasta que reenvíes la orden con la línea en paid

payment_status, estado de pago de la orden: paid (pagada) o unpaid (no pagada).

shipment_status, estado del despacho de la orden:

ValorSignificado
noinformationSin información (default)
inpreparationEn preparación
intransitEn tránsito
deliveredEntregada
returnedDevuelta
returninprocessDevolución en curso
inmediationEn mediación
faileddeliveryEntrega fallida
notrequiredNo requiere despacho
canceledCancelada

Fechas

Las fechas van en formato RFC 3339 con offset explícito: 2026-09-24T15:30:00-03:00 o 2026-09-24T18:30:00Z. Sin offset, se rechazan.

  • Separador T entre fecha y hora (no un espacio)
  • Segundos obligatorios; fracción de segundo opcional (15:30:00.123-03:00)
  • Offset Z o ±HH:MM. No se aceptan -03 ni -0300

¿Por qué el offset? Chile cambia de horario dos veces al año, y una hora local sin zona es ambigua en el cambio: puede correr una orden de día sin que nadie lo note.

Además, purchase_date no puede estar más de 24 horas en el futuro ni tener más de 2 años de antigüedad.

Montos

Todos los montos van en CLP, con IVA incluido, y no pueden ser negativos.

  • El total pagado de una línea es unit_price × quantity − discount.
  • shipping_cost es tu costo de despacho; shipping_income, lo que te pagó el comprador por el despacho. Tu costo neto de despacho es la diferencia. No mezcles el ingreso en shipping_cost: invertiría el sentido del número en tu rentabilidad.
  • payment_fee es la comisión de la pasarela. Si no la envías, la comisión queda como desconocida.
  • Wivo reparte shipping_cost, shipping_income y payment_fee entre las líneas de la orden en proporción al total de cada línea: la línea más cara carga más.

Por ejemplo, en la orden WEB-1001 del ejemplo válido las líneas suman $23.980 y $8.990 (total $32.970). El despacho de $3.990 queda en $2.902,04 para la primera y $1.087,96 para la segunda.

Identificadores y textos

  • Los ids (order_id, product_id, sku, variant.id, shopper.id) tienen hasta 64 caracteres y no pueden llevar comilla simple (') ni caracteres de control.
  • Los textos (name, brand, category, variant.name, shopper.name) tienen hasta 255 caracteres y sí aceptan la comilla simple: “María O’Higgins” es válido.
  • El source_id es un UUID de 36 caracteres, con guiones. Da lo mismo si viene en mayúsculas o minúsculas.
  • Un order_id no puede repetirse dentro del mismo lote.

Ejemplos

cURL

curl -X POST "https://api.wivoanalytics.com/orders" \
  -H "Ocp-Apim-Subscription-Key: TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "source_id": "TU_SOURCE_ID_AQUI",
    "orders": [
      {
        "order_id": "WEB-1001",
        "purchase_date": "2026-09-20T15:30:00-03:00",
        "last_update_date": "2026-09-20T16:05:00-03:00",
        "currency": "CLP",
        "payment_status": "paid",
        "shipment_status": "delivered",
        "shipping_cost": 3990,
        "shipping_income": 2990,
        "payment_fee": 1250,
        "items": [
          {
            "product_id": "P-10",
            "sku": "POL-AZ-M",
            "name": "Polera azul",
            "quantity": 2,
            "unit_price": 12990,
            "discount": 2000,
            "status": "paid"
          }
        ]
      }
    ]
  }'

Si tus datos traen comilla simple (por ejemplo, un comprador “O’Higgins”), en la terminal envía el cuerpo desde un archivo, así la comilla no corta el comando:

curl -X POST "https://api.wivoanalytics.com/orders" \
  -H "Ocp-Apim-Subscription-Key: TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  --data @ordenes.json

Python (requests)

import requests

lote = {
    "source_id": "TU_SOURCE_ID_AQUI",
    "orders": [
        {
            "order_id": "WEB-1001",
            "purchase_date": "2026-09-20T15:30:00-03:00",
            "last_update_date": "2026-09-20T16:05:00-03:00",
            "currency": "CLP",
            "payment_status": "paid",
            "items": [
                {"product_id": "P-10", "name": "Polera azul", "quantity": 2,
                 "unit_price": 12990, "discount": 2000, "status": "paid"},
            ],
        }
    ],
}

response = requests.post(
    "https://api.wivoanalytics.com/orders",
    headers={"Ocp-Apim-Subscription-Key": "TU_API_KEY_AQUI"},
    json=lote,
    timeout=30,
)
cuerpo = response.json()

if response.status_code == 202:
    print(f"{cuerpo['accepted']} órdenes recibidas")
elif response.status_code == 422:
    for error in cuerpo["errors"]:
        print(f"Orden {error['order_id']} · {error['field']}: {error['error']}")
else:
    print(response.status_code, cuerpo)

Node.js (fetch)

const lote = {
  source_id: "TU_SOURCE_ID_AQUI",
  orders: [
    {
      order_id: "WEB-1001",
      purchase_date: "2026-09-20T15:30:00-03:00",
      last_update_date: "2026-09-20T16:05:00-03:00",
      currency: "CLP",
      payment_status: "paid",
      items: [
        { product_id: "P-10", name: "Polera azul", quantity: 2,
          unit_price: 12990, discount: 2000, status: "paid" },
      ],
    },
  ],
};

const response = await fetch("https://api.wivoanalytics.com/orders", {
  method: "POST",
  headers: {
    "Ocp-Apim-Subscription-Key": "TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(lote),
});
const cuerpo = await response.json();

if (response.status === 202) {
  console.log(`${cuerpo.accepted} órdenes recibidas`);
} else if (response.status === 422) {
  for (const e of cuerpo.errors) console.error(`Orden ${e.order_id} · ${e.field}: ${e.error}`);
} else {
  console.error(response.status, cuerpo);
}

Ejemplo válido completo

Dos órdenes: una pagada y entregada, con todos los campos opcionales, y una no pagada con su única línea cancelada, solo con los obligatorios.

{
  "source_id": "3f2b6c1e-8a4d-4c2e-9b7a-1d2e3f4a5b6c",
  "orders": [
    {
      "order_id": "WEB-1001",
      "purchase_date": "2026-09-20T15:30:00-03:00",
      "last_update_date": "2026-09-20T16:05:00-03:00",
      "currency": "CLP",
      "payment_status": "paid",
      "shipment_status": "delivered",
      "shopper": {"id": "C-77", "name": "María O'Higgins"},
      "shipping_cost": 3990,
      "payment_fee": 1250,
      "items": [
        {"product_id": "P-10", "sku": "POL-AZ-M", "name": "Polera azul", "variant": {"id": "P-10-M", "name": "M"}, "brand": "Marca Propia", "category": "Poleras", "quantity": 2, "unit_price": 12990, "discount": 2000, "status": "paid"},
        {"product_id": "P-22", "name": "Gorro de lana", "quantity": 1, "unit_price": 8990, "status": "paid"}
      ]
    },
    {
      "order_id": "WEB-1002",
      "purchase_date": "2026-09-21T10:00:00-03:00",
      "last_update_date": "2026-09-22T09:00:00-03:00",
      "currency": "CLP",
      "payment_status": "unpaid",
      "items": [
        {"product_id": "P-10", "name": "Polera azul", "quantity": 1, "unit_price": 12990, "status": "canceled"}
      ]
    }
  ]
}

Ejemplo inválido

Cinco errores en una sola orden: fecha sin offset, un estado de pago que no es de Wivo (pagado), cantidad 0, precio negativo y un campo que no existe en el contrato (costo, que además es justo lo que no se envía).

{
  "source_id": "3f2b6c1e-8a4d-4c2e-9b7a-1d2e3f4a5b6c",
  "orders": [
    {
      "order_id": "WEB-2001",
      "purchase_date": "2026-09-20T15:30:00",
      "last_update_date": "2026-09-20T16:05:00-03:00",
      "currency": "CLP",
      "payment_status": "pagado",
      "items": [
        {"product_id": "P-10", "name": "Polera azul", "quantity": 0, "unit_price": -5, "status": "paid", "costo": 4000}
      ]
    }
  ]
}

La respuesta es un 422 con un error por problema, y no entra ninguna orden:

{
  "errors": [
    {"index": 0, "order_id": "WEB-2001", "field": "/orders/0/purchase_date", "error": "Debe ser una fecha RFC 3339 con offset explícito, p. ej. 2026-09-24T15:30:00-03:00."},
    {"index": 0, "order_id": "WEB-2001", "field": "/orders/0/payment_status", "error": "Debe ser uno de: paid, unpaid."},
    {"index": 0, "order_id": "WEB-2001", "field": "/orders/0/items/0/costo", "error": "El campo 'costo' no existe en el contrato v1."},
    {"index": 0, "order_id": "WEB-2001", "field": "/orders/0/items/0/quantity", "error": "Debe ser mayor o igual a 1."},
    {"index": 0, "order_id": "WEB-2001", "field": "/orders/0/items/0/unit_price", "error": "Debe ser mayor o igual a 0."}
  ],
  "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

JSON Schema v1

Es el schema con el que la API valida cada lote (JSON Schema draft 2020-12). Puedes usarlo para validar tus lotes antes de enviarlos. Las reglas que el schema no alcanza a expresar están más abajo.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://api.wivoanalytics.com/schemas/orders-v1.schema.json",
  "title": "Wivo — lote de órdenes v1 (Tienda propia API)",
  "description": "Cuerpo de POST /orders (contrato v1).",
  "type": "object",
  "additionalProperties": false,
  "required": ["source_id", "orders"],
  "properties": {
    "source_id": {
      "description": "UUID (identifier) de la fuente 'Tienda propia (API)' del tenant. Se verifica contra el tenant de la API key.",
      "type": "string",
      "format": "uuid"
    },
    "orders": {
      "type": "array",
      "minItems": 1,
      "maxItems": 100,
      "items": { "$ref": "#/$defs/order" }
    }
  },
  "$defs": {
    "text": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255,
      "pattern": "^[^\\u0000-\\u001f]*$"
    },
    "id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "description": "Sin caracteres de control ni comilla simple.",
      "pattern": "^[^\\u0000-\\u001f']*$"
    },
    "amount": {
      "type": "number",
      "minimum": 0,
      "maximum": 100000000000
    },
    "datetime": {
      "description": "ISO 8601 / RFC 3339 CON offset explícito (p. ej. 2026-09-24T15:30:00-03:00). Sin offset se rechaza.",
      "type": "string",
      "format": "date-time"
    },
    "order": {
      "type": "object",
      "additionalProperties": false,
      "required": ["order_id", "purchase_date", "last_update_date", "currency", "payment_status", "items"],
      "properties": {
        "order_id": {
          "$ref": "#/$defs/id",
          "description": "ID de la orden en la tienda. Único por fuente. Reenviar el mismo order_id REEMPLAZA la orden completa."
        },
        "purchase_date": {
          "$ref": "#/$defs/datetime",
          "description": "Fecha de compra. INMUTABLE: si cambia más de 5 días respecto de lo ya enviado, la orden se duplica."
        },
        "last_update_date": { "$ref": "#/$defs/datetime" },
        "currency": { "const": "CLP" },
        "payment_status": { "enum": ["paid", "unpaid"] },
        "shipment_status": {
          "enum": ["noinformation", "inpreparation", "intransit", "delivered", "returned", "returninprocess", "inmediation", "faileddelivery", "notrequired", "canceled"],
          "default": "noinformation"
        },
        "shopper": {
          "type": "object",
          "additionalProperties": false,
          "required": ["id"],
          "properties": {
            "id": { "$ref": "#/$defs/id" },
            "name": { "$ref": "#/$defs/text" }
          }
        },
        "shipping_cost": {
          "$ref": "#/$defs/amount",
          "description": "Lo que le cuesta AL SELLER despachar la orden (no lo que paga el comprador). Wivo lo prorratea entre las líneas por su total."
        },
        "shipping_income": {
          "$ref": "#/$defs/amount",
          "description": "Lo que PAGÓ EL COMPRADOR por el despacho, IVA incluido. Va a shipping_import, prorrateado entre las líneas por su total. El costo neto de despacho del seller = shipping_cost − shipping_income."
        },
        "payment_fee": {
          "$ref": "#/$defs/amount",
          "description": "Comisión de la pasarela de pago de la orden (Webpay, Mercado Pago, etc.). Va a market_fee, prorrateada entre las líneas por su total."
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "maxItems": 200,
          "description": "TODAS las líneas de la orden, siempre. Una actualización trae la orden completa.",
          "items": { "$ref": "#/$defs/item" }
        }
      }
    },
    "item": {
      "type": "object",
      "additionalProperties": false,
      "required": ["product_id", "name", "quantity", "unit_price", "status"],
      "properties": {
        "product_id": {
          "$ref": "#/$defs/id",
          "description": "ID del producto en la tienda. Es el 'ID Producto' con el que aparece en el Maestro de Wivo."
        },
        "sku": { "$ref": "#/$defs/id" },
        "name": { "$ref": "#/$defs/text" },
        "variant": {
          "type": "object",
          "additionalProperties": false,
          "required": ["id"],
          "properties": {
            "id": { "$ref": "#/$defs/id" },
            "name": { "$ref": "#/$defs/text" }
          }
        },
        "brand": { "$ref": "#/$defs/text" },
        "category": { "$ref": "#/$defs/text" },
        "quantity": { "type": "integer", "minimum": 1, "maximum": 100000 },
        "unit_price": {
          "$ref": "#/$defs/amount",
          "description": "Precio unitario de lista, IVA incluido, ANTES del descuento."
        },
        "discount": {
          "$ref": "#/$defs/amount",
          "default": 0,
          "description": "Descuento TOTAL de la línea (no unitario), IVA incluido. No puede superar unit_price × quantity."
        },
        "status": { "enum": ["paid", "canceled", "returned", "pending"] }
      }
    }
  }
}

Reglas que el schema no expresa

La API también rechaza con 422:

  1. Un order_id repetido dentro del mismo lote.
  2. Un discount mayor que unit_price × quantity.
  3. Un purchase_date más de 24 horas en el futuro o con más de 2 años de antigüedad.
  4. Un last_update_date anterior a purchase_date.
  5. Fechas y UUID fuera del formato estricto: los format date-time y uuid se validan de verdad. Fecha con separador T (no espacio), segundos obligatorios, fracción opcional y offset Z o ±HH:MM; la T y la Z se aceptan también en minúscula. UUID canónico de 36 caracteres, sin prefijo urn:uuid: ni llaves, en mayúsculas o minúsculas.

Si validas con una biblioteca de JSON Schema, activa la validación de formatos (en Python, FormatChecker; en Node, ajv-formats): sin ella, una fecha sin offset pasaría tu validación y la API la rechazaría.

Respuestas del servidor

Toda respuesta de la API lleva un correlation_id. Guárdalo: es lo que nos sirve para rastrear un envío si nos escribes.

CódigoSignificadoReintentar
202Lote recibido. Las órdenes aparecen en la app unos minutos despuésNo
400El cuerpo está vacío o no es un JSON válidoNo, corrige el cuerpo
401API Key ausente o inválidaNo
403Cuenta sin plan Pro, suscripción revocada, o un source_id que no es una Tienda propia (API) activa de tu cuenta. También cuando se agota el tope diarioNo
413Más de 100 órdenes o más de 1 MB en el loteNo: divide el lote
422Lote inválido. No entró ninguna ordenNo: corrige y reenvía el lote completo
429Superaste las 30 solicitudes por minutoSí, después de lo que indique el header Retry-After
503No pudimos recibir el lote en este momento (retryable: true)Sí, el mismo lote

202 Accepted

{
  "accepted": 1,
  "order_ids": ["WEB-1001"],
  "dag_run_id": "tpapi__3f2b6c1e-8a4d-4c2e-9b7a-1d2e3f4a5b6c__f5610004e788a9df4b644366ee7112b4ffd54eb640d2d2eed526b5e0fd874f31",
  "duplicate": false,
  "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
CampoDescripción
acceptedCantidad de órdenes recibidas
order_idsLos order_id recibidos, en el orden del lote
dag_run_idIdentificador del procesamiento del lote. Cítalo si nos escribes por un envío
duplicatetrue si ya habíamos recibido exactamente este mismo lote: no se procesa dos veces

202 quiere decir que recibimos el lote, no que las órdenes ya estén en tus reportes: aparecen en la app unos minutos después.

422 Unprocessable Entity

Cada error trae:

CampoDescripción
indexPosición de la orden en orders (desde 0). null si el error es del lote, por ejemplo en /source_id
order_idEl order_id de esa orden, o null si no vino o no es válido
fieldJSON Pointer al campo con error, por ejemplo /orders/0/items/1/unit_price. Un campo que sobra apunta a ese campo: /orders/0/items/0/costo
errorQué está mal

La lista trae como máximo 100 errores. Si había más, la respuesta agrega "truncated": true. El ejemplo completo está en Ejemplo inválido.

413 Payload Too Large

{
  "error": "El lote trae 101 órdenes y el máximo por request es 100. Divide el envío en lotes de hasta 100 órdenes.",
  "max_orders": 100,
  "correlation_id": "a1b2c3d4-..."
}

Si el lote pesa más de 1 MB, la respuesta trae "max_bytes": 1048576 en lugar de max_orders. En los dos casos, reenviar el mismo lote no va a funcionar: divídelo.

403 Forbidden

{
  "error": "La fuente '3f2b6c1e-8a4d-4c2e-9b7a-1d2e3f4a5b6c' no es una fuente 'Tienda propia (API)' activa de tu cuenta.",
  "correlation_id": "a1b2c3d4-..."
}

503 Service Unavailable

{
  "error": "No pudimos recibir el lote en este momento. Reintenta el mismo lote: si el contenido es idéntico, Wivo reconoce el reintento y no lo procesa dos veces.",
  "retryable": true,
  "correlation_id": "a1b2c3d4-..."
}

Límites de uso

  • 100 órdenes por solicitud, y hasta 200 líneas por orden
  • 1 MB por solicitud (1.048.576 bytes)
  • 30 solicitudes por minuto por API Key
  • 5.000 solicitudes por día

Pasarte de 100 órdenes o de 1 MB retorna 413; de 30 solicitudes por minuto, 429; y agotar las 5.000 del día, 403 hasta que se renueve la cuota.

Con lotes de 100 órdenes, 30 solicitudes por minuto son 3.000 órdenes por minuto. Enviar un lote cada 2 segundos te mantiene bajo el límite.

Reintentos e idempotencia

Reenviar es seguro. Hay dos garantías, y juntas cubren cualquier reintento:

  • El mismo lote no se procesa dos veces. Si reenvías exactamente el mismo contenido (aunque cambien el orden de las claves o los espacios), la API responde 202 con "duplicate": true y el mismo dag_run_id.
  • Reenviar una orden la reemplaza. Si el contenido cambió, es un lote nuevo, y cada order_id reemplaza a su versión anterior. Nunca se duplica.

Qué hacer según la respuesta:

RespuestaQué hacer
503 con retryable: trueReintenta el mismo lote, con espera creciente (2, 4, 8… segundos)
429Espera los segundos del header Retry-After y reintenta
Sin respuesta (timeout, corte de red)Reintenta el mismo lote: si ya había llegado, responde duplicate: true
400, 401, 403, 413, 422No reintentes igual: corrige primero. Reintentar el mismo lote da el mismo error

Recetas

Enviar muchas órdenes, con reintentos

Divide tus órdenes en lotes de 100, envíalos a un ritmo que respete el límite por minuto y reintenta solo lo que se arregla reintentando.

import time
import requests

URL = "https://api.wivoanalytics.com/orders"
HEADERS = {"Ocp-Apim-Subscription-Key": "TU_API_KEY_AQUI"}
SOURCE_ID = "TU_SOURCE_ID_AQUI"


def enviar_lote(ordenes, intentos=5):
    lote = {"source_id": SOURCE_ID, "orders": ordenes}
    espera = 2
    for intento in range(intentos):
        try:
            response = requests.post(URL, headers=HEADERS, json=lote, timeout=30)
        except requests.RequestException:
            response = None  # sin respuesta: reenviar el mismo lote es seguro

        if response is not None:
            if response.status_code == 202:
                return response.json()
            if response.status_code == 429:
                espera = int(response.headers.get("Retry-After", espera))
            elif response.status_code != 503:
                # 400, 401, 403, 413 y 422 no se arreglan reintentando
                raise RuntimeError(f"Lote rechazado ({response.status_code}): {response.text}")

        time.sleep(espera)
        espera = min(espera * 2, 60)

    raise RuntimeError("No se pudo enviar el lote: reintenta más tarde con el mismo contenido")


ordenes = [...]  # las órdenes de tu tienda, cada una completa

for i in range(0, len(ordenes), 100):
    resultado = enviar_lote(ordenes[i:i + 100])
    print(resultado["accepted"], "órdenes recibidas", "(ya estaban)" if resultado["duplicate"] else "")
    time.sleep(2)  # 30 solicitudes por minuto

Si un lote de 100 órdenes con muchas líneas pesa más de 1 MB, la API responde 413 con max_bytes: usa lotes más chicos.

Actualizar una orden: cancelación o devolución

Reenvía la orden completa, con el mismo order_id y el mismo purchase_date, las líneas en su nuevo estado y un last_update_date nuevo. Por ejemplo, si el comprador devolvió el gorro de la orden WEB-1001:

{
  "source_id": "TU_SOURCE_ID_AQUI",
  "orders": [
    {
      "order_id": "WEB-1001",
      "purchase_date": "2026-09-20T15:30:00-03:00",
      "last_update_date": "2026-09-25T11:00:00-03:00",
      "currency": "CLP",
      "payment_status": "paid",
      "shipment_status": "delivered",
      "shipping_cost": 3990,
      "payment_fee": 1250,
      "items": [
        {"product_id": "P-10", "name": "Polera azul", "quantity": 2, "unit_price": 12990, "discount": 2000, "status": "paid"},
        {"product_id": "P-22", "name": "Gorro de lana", "quantity": 1, "unit_price": 8990, "status": "returned"}
      ]
    }
  ]
}

La polera viaja igual, aunque no haya cambiado: si la omites, esa línea desaparece de Wivo.

Cargar el costo de tus productos

El costo no va en la orden. Los productos de tus órdenes aparecen en el Maestro con Marketplace “Tienda propia”, y ahí les cargas el costo como en cualquier otro canal: con la planilla del Maestro en la app, o por API.

Para hacerlo por API, lee los productos de tu tienda que aún no tienen costo con el Maestro de productos por API, filtrando por tu source_id (es el ID Cuenta de tus productos):

curl "https://api.wivoanalytics.com/master/products?source_id=TU_SOURCE_ID_AQUI&withoutCost=true" \
  -H "Ocp-Apim-Subscription-Key: TU_API_KEY_AQUI"

Y devuelve los costos con la carga de costos por API. El product_id que envías en cada línea es el ID Producto del Maestro.

Preguntas frecuentes

¿Cuánto tardan mis órdenes en aparecer en Wivo? Unos minutos después del 202. El 202 confirma que recibimos el lote; el procesamiento viene después.

¿Cómo sé que mis órdenes están llegando? En Integraciones, tu Tienda propia (API) muestra cuándo llegó la última orden. Si todavía no llega ninguna, lo dice.

¿Qué pasa si recibí un 202 pero las órdenes no aparecen? En esta versión no hay un endpoint para consultar el estado de un lote. Si pasado un rato no las ves, escríbenos por el chat de la app o a soporte@wivoanalytics.com con el correlation_id y el dag_run_id de la respuesta, y lo revisamos.

¿Qué pasa si envío dos veces el mismo lote? Nada malo. La API responde 202 con "duplicate": true y el lote no se procesa dos veces.

¿Puedo enviar solo la línea que cambió? No. Cada envío trae la orden completa, porque reenviar un order_id reemplaza la orden entera: una línea que no viene, desaparece.

¿Puedo corregir la fecha de compra de una orden? Evítalo: purchase_date tiene que ser siempre la misma. Si la corres más de 5 días, la orden aparece dos veces.

¿Puedo borrar una orden? No en esta versión. Reenviarla con sus líneas en canceled cambia su estado, pero no la borra.

¿Puedo enviar el costo de los productos en la orden? No, y un campo costo rechaza el lote. El costo vive en el Maestro, como en todos tus canales: así hay una sola fuente de verdad. Ver Cargar el costo de tus productos.

¿Por qué mi order_id con comilla simple da error? Los ids no aceptan comilla simple, para que el order_id quede en Wivo exactamente igual que en tu tienda y puedas cruzarlos. Los textos, como el nombre del comprador, sí la aceptan.

¿Puedo enviar órdenes antiguas? Sí, con purchase_date de hasta 2 años atrás, en lotes de 100 y respetando los límites de uso. No hay una carga histórica masiva aparte.

¿Puedo enviar órdenes en otra moneda o de otro país? Por ahora no: durante la beta la API es solo para cuentas de Chile y montos en CLP.

¿Este recurso consume mi cuota de la API de datos? No. Viene incluido en el plan Pro, no necesita el add-on y tiene su propio límite de 30 solicitudes por minuto.