Desarrolladores · Zent Connect

Zent para desarrolladores

Última actualización: 3 de noviembre de 2026

Si tu tienda está hecha a medida (no en Tiendanube ni en Shopify), la conectás vos: mandás tu catálogo, tus clientes, tus órdenes y tus carritos en el formato de Zent con el SDK @usezent/connect, y Zent arma las campañas de lifecycle como con cualquier otra tienda. Antes de conectar, Zent revisa lo que cargaste y te dice qué está mal, dónde y cómo arreglarlo.

Cómo se conecta

  • Creá la tienda en Zent: Conectar tienda › Tienda propia (nombre, dirección, moneda y zona horaria). Te da la clave zk_live_…, que se ve una sola vez.
  • Cargá la historia: productos, clientes, órdenes y carritos (el SDK lo hace en el orden correcto).
  • Conectá: Zent corre los chequeos. Con un error, la tienda no se conecta y el reporte dice qué arreglar; sin errores, queda conectada y Zent arma sus campañas.
  • Mandá cada cambio cuando pasa (tiempo real): una orden cobrada, un cliente nuevo, un cambio de stock, un carrito.
  • Verificá tu dominio de envío en Zent › Dominios: los mails de una tienda propia salen sólo desde el dominio de la tienda (por ejemplo, mail.mitienda.com.ar).

Instalar el SDK

Node 20.19 o más nuevo, sin dependencias. Valida cada registro antes de mandarlo con las mismas reglas que Zent.

npm install @usezent/connect
carga inicial
import { ZentConnect } from '@usezent/connect';

const zent = new ZentConnect({ apiKey: process.env.ZENT_API_KEY });

// 1. La historia: arrays o iterables async (se parten en lotes de 500).
const result = await zent.backfill({
  products: allProducts(),
  customers: allCustomers(),
  orders: allOrders(),
  checkouts: openCheckouts(),
});

// 2. Los chequeos de Zent: si algo está mal, la tienda no se conecta y te dice qué arreglar.
console.log(result.report.errors, result.report.warnings);

La clave

Cada pedido lleva Authorization: Bearer zk_live_…. La clave identifica a la tienda: no hace falta mandar nada más. Guardala en el servidor (ZENT_API_KEY), nunca en el navegador ni en el repositorio. Si se filtra, generá otra en Zent: la anterior deja de valer en el acto.

Productos

El catálogo entero, también lo que ya no vendés (published: false): las órdenes viejas lo referencian. Cada combinación que se compra por separado es una variante.

CampoTipoQué es
idobligatorioid (texto o entero)Id del producto en tu sistema (texto o número, hasta 64 caracteres).
nameobligatoriotexto (hasta 300)Nombre visible del producto.
urlobligatorioURLURL pública de la página del producto, en el dominio de tu tienda. Los mails llevan acá.
handletexto (hasta 200) · nullEl slug de la URL, si lo tenés.
brandtexto (hasta 120) · nullMarca.
imageslista de URL httpsURLs de las fotos en orden (https). La primera es la principal.
publishedtrue / falsedefault truefalse = no está a la venta; Zent no lo recomienda.
created_atfecha ISO 8601 · nullCuándo se dio de alta el producto.
categorieslista de objetosCategorías del producto. Zent las usa para elegir qué recomendar.
categories[].idobligatorioid (texto o entero)Id de la categoría en tu sistema.
categories[].nameobligatoriotexto (hasta 200)Nombre visible de la categoría.
categories[].parent_idid (texto o entero) · nullId de la categoría madre, si es una subcategoría.
variantsobligatoriolista de objetosAl menos una. Cada combinación que se compra por separado (talle, color…).
variants[].idobligatorioid (texto o entero)Id de la variante. Si tu tienda no maneja variantes, mandá una sola con el mismo id que el producto.
variants[].skutexto (hasta 120) · nullSKU, si lo tenés.
variants[].priceobligatoriomonto (número ≥ 0)Precio de venta actual, en la moneda de la tienda.
variants[].compare_at_pricemonto (número ≥ 0) · nullPrecio de lista antes de una rebaja (el tachado). Vacío si no está en oferta.
variants[].stockdeclararentero ≥ 0 · nullUnidades disponibles. null = ilimitado (no controlás stock). Con 0 no se recomienda en los mails.
variants[].visibletrue / falsedefault truefalse = la variante no se puede comprar en la tienda.
variants[].optionsobjeto {atributo: valor}Atributos de la variante como objeto: {"Talle": "M", "Color": "Rojo"}.
POST /api/connect/v1/products · un registro de data
{
  "id": "remera-basica",
  "name": "Remera básica",
  "url": "https://mitienda.com.ar/productos/remera-basica",
  "images": [
    "https://cdn.mitienda.com.ar/remera-basica-1.jpg"
  ],
  "categories": [
    {
      "id": "remeras",
      "name": "Remeras"
    }
  ],
  "variants": [
    {
      "id": "remera-basica-m",
      "price": 15000,
      "stock": 12,
      "options": {
        "Talle": "M"
      }
    },
    {
      "id": "remera-basica-l",
      "price": 15000,
      "stock": 0,
      "options": {
        "Talle": "L"
      }
    }
  ]
}

Clientes

Quién compró o se registró. accepts_marketing es lo que eligió la persona: Zent no lo supone por vos.

CampoTipoQué es
idobligatorioid (texto o entero)Id del cliente en tu sistema.
emailemail · nullMail del cliente. Sin mail se guarda, pero nunca recibe campañas.
nametexto (hasta 200) · nullNombre y apellido.
phonetexto (hasta 40) · nullTeléfono, como lo tengas.
created_atobligatoriofecha ISO 8601Alta del cliente en tu tienda (no en Zent). Decide quién recibe la Bienvenida.
accepts_marketingdeclarartrue / false · nulltrue = aceptó recibir novedades; false = dijo que no; null = nunca se lo preguntaste. Obligatorio: Zent no lo supone.
accepts_marketing_atfecha ISO 8601 · nullCuándo dio (o sacó) el permiso, si lo guardás.
POST /api/connect/v1/customers · un registro de data
{
  "id": "1042",
  "email": "ana@ejemplo.com",
  "name": "Ana Pérez",
  "created_at": "2026-10-01T14:30:00-03:00",
  "accepts_marketing": true
}

Órdenes

Todas las órdenes, con sus líneas. Sólo payment_status = paid cuenta como venta.

CampoTipoQué es
idobligatorioid (texto o entero)Id de la orden en tu sistema.
numberentero ≥ 1 · nullNúmero de orden visible para el cliente, si es distinto del id.
customerobjetoQuién compró: el id de Clientes y/o el mail.
customer.idid (texto o entero) · nullId del cliente (el `id` que mandás en Clientes). Vacío en una compra como invitado.
customer.emailemail · nullMail con el que compró. Con él se atribuyen las ventas a los mails.
created_atobligatoriofecha ISO 8601Cuándo se hizo la compra.
currencyobligatoriomoneda ISO 4217Moneda (ISO 4217, ej. ARS). Tiene que ser la de tu tienda en Zent.
payment_statusobligatoriouno de los valoresEstado del pago.
  • paid — Cobrada. Es la única que cuenta como venta.
  • pending — Esperando el pago (transferencia, efectivo, link de pago sin usar).
  • authorized — Autorizada por la tarjeta pero todavía sin cobrar.
  • partially_refunded — Cobrada y devuelta en parte.
  • refunded — Cobrada y devuelta entera.
  • voided — Anulada o rechazada: nunca se cobró.
paid_atfecha ISO 8601 · nullCuándo se cobró. Obligatorio si payment_status es paid.
statusuno de los valoresdefault "open"Estado de la orden.
  • open — Abierta: en preparación o en camino.
  • closed — Cerrada: entregada o archivada.
  • cancelled — Cancelada.
cancelled_atfecha ISO 8601 · nullCuándo se canceló.
subtotalobligatoriomonto (número ≥ 0)Suma de las líneas (precio × cantidad), antes de descuentos y envío.
discountsobjetoDescuentos de la orden, por tipo.
discounts.couponmonto (número ≥ 0)default 0Descuento por cupón.
discounts.promotionmonto (número ≥ 0)default 0Descuento por promociones automáticas (2x1, % por categoría…).
discounts.gatewaymonto (número ≥ 0)default 0Descuento por medio de pago (transferencia, efectivo…).
shippingmonto (número ≥ 0)default 0Lo que pagó el cliente por el envío.
totalobligatoriomonto (número ≥ 0)Lo que pagó en total.
coupon_codeslista de texto (hasta 60)Códigos de cupón usados. Con ellos Zent atribuye las ventas de los mails con cupón.
itemsobligatoriolista de objetosLas líneas de la orden.
items[].product_idobligatorioid (texto o entero)Id del producto (el mismo `id` que mandás en Productos).
items[].variant_idid (texto o entero) · nullId de la variante (el mismo `id` de `variants`). Vacío si el producto no tiene variantes.
items[].nameobligatoriotexto (hasta 300)Nombre del producto como figura en la orden.
items[].skutexto (hasta 120) · nullSKU de la línea.
items[].quantityobligatorioentero ≥ 1Unidades compradas.
items[].unit_priceobligatoriomonto (número ≥ 0)Precio por unidad que pagó, antes de descuentos de la orden.
items[].image_urlURL https · nullFoto del producto (https).
items[].zent_offerid (texto o entero) · nullSi la línea la sumó la oferta del cross-sell de Zent en tu checkout: el id de la oferta (offer.id). Con eso esa venta se le atribuye a Zent.
shipped_atfecha ISO 8601 · nullCuándo se despachó.
trackingobjeto · nullSeguimiento del envío.
tracking.numbertexto (hasta 120) · nullNúmero de seguimiento del envío.
tracking.urlURL · nullLink para seguir el envío (va en el mail de Post-compra).
POST /api/connect/v1/orders · un registro de data
{
  "id": "A-20931",
  "customer": {
    "id": "1042",
    "email": "ana@ejemplo.com"
  },
  "created_at": "2026-10-28T19:02:00-03:00",
  "paid_at": "2026-10-28T19:03:10-03:00",
  "currency": "ARS",
  "payment_status": "paid",
  "status": "open",
  "subtotal": 30000,
  "discounts": {
    "coupon": 3000
  },
  "shipping": 2500,
  "total": 29500,
  "coupon_codes": [
    "HOLA10"
  ],
  "items": [
    {
      "product_id": "remera-basica",
      "variant_id": "remera-basica-m",
      "name": "Remera básica · M",
      "quantity": 2,
      "unit_price": 15000
    }
  ]
}

Carritos

Carritos con mail que no terminaron en compra. Cuando un carrito se convierte, alcanza con mandar la orden: Zent lo cierra solo (por el mail o el cliente).

CampoTipoQué es
idobligatorioid (texto o entero)Id del carrito en tu sistema.
emailobligatorioemailMail que dejó en el checkout. Sin mail no hay a quién escribirle.
nametexto (hasta 200) · nullNombre, si lo dejó.
phonetexto (hasta 40) · nullTeléfono, si lo dejó.
customer_idid (texto o entero) · nullId del cliente, si estaba logueado.
created_atobligatoriofecha ISO 8601Cuándo empezó el checkout.
updated_atobligatoriofecha ISO 8601Último cambio del carrito.
recovery_urlobligatorioURLLink que vuelve a abrir ESE carrito con sus productos, en el dominio de tu tienda. Es el botón del mail.
currencyobligatoriomoneda ISO 4217Moneda (ISO 4217). La de tu tienda.
totalobligatoriomonto (número ≥ 0)Total del carrito.
accepts_marketingtrue / false · nullSi marcó «quiero recibir novedades» en el checkout. null = no se lo preguntaste.
itemsobligatoriolista de objetosLos productos del carrito.
items[].product_idobligatorioid (texto o entero)Id del producto (el mismo `id` que mandás en Productos).
items[].variant_idid (texto o entero) · nullId de la variante (el mismo `id` de `variants`). Vacío si el producto no tiene variantes.
items[].nameobligatoriotexto (hasta 300)Nombre del producto como figura en la orden.
items[].skutexto (hasta 120) · nullSKU de la línea.
items[].quantityobligatorioentero ≥ 1Unidades compradas.
items[].unit_priceobligatoriomonto (número ≥ 0)Precio por unidad que pagó, antes de descuentos de la orden.
items[].image_urlURL https · nullFoto del producto (https).
items[].zent_offerid (texto o entero) · nullEl id de la oferta del cross-sell de Zent, si la sumó ella. Opcional: la venta se atribuye por la orden.
POST /api/connect/v1/checkouts · un registro de data
{
  "id": "cart_88f1",
  "email": "ana@ejemplo.com",
  "created_at": "2026-11-01T21:10:00-03:00",
  "updated_at": "2026-11-01T21:14:00-03:00",
  "recovery_url": "https://mitienda.com.ar/carrito/recuperar/cart_88f1",
  "currency": "ARS",
  "total": 15000,
  "accepts_marketing": true,
  "items": [
    {
      "product_id": "remera-basica",
      "variant_id": "remera-basica-l",
      "name": "Remera básica · L",
      "quantity": 1,
      "unit_price": 15000
    }
  ]
}

Ids

  • Un id es un texto o un entero de hasta 64 caracteres: el de tu base (un número, un UUID, un SKU). Se guarda tal cual.
  • Usá los mismos ids en todos lados: items[].product_id y variant_id son los id que mandás en Productos.
  • Si un producto no tiene variantes, mandá una con el mismo id que el producto y usá ese id como variant_id.
  • Mandar un registro que ya está lo actualiza (upsert por id): podés reenviar sin duplicar.

Tiempo real

Con la tienda conectada, cada cambio que mandás entra al momento. Una orden cobrada en las últimas 48 horas recalcula al comprador y lo que le toca; un cliente registrado en la última hora puede recibir la Bienvenida. Lo más viejo entra como historia y lo toma la vuelta diaria. Que un error de Zent nunca te rompa la compra: atrapalo y reintentá.

// Después de crear o cobrar una orden:
await zent.orders.upsert([toZentOrder(order)]);

// Cuando se registra un cliente:
await zent.customers.upsert([toZentCustomer(customer)]);

// Cuando cambia el precio o el stock:
await zent.products.upsert([toZentProduct(product)]);

// Cuando un carrito queda con mail y sin comprar (por ejemplo, a la hora):
await zent.checkouts.upsert([toZentCheckout(cart)]);

Cupones

Los cupones de tu tienda los guarda Zent (los de cada mail, el del pop-up, el del carrito, el beneficio por monto). Tu checkout los valida y los canjea con el SDK, y Zent los empieza a usar recién cuando lo activás: hasta entonces los mails salen sin cupón.

  • Validar cuando el comprador escribe un código: zent.coupons.validate(code, { subtotal }). Un cupón que no vale no tira error: vuelve valid: false con el motivo y un texto para mostrarle.
  • Canjear al crear la orden: zent.coupons.redeem(code, { orderId, subtotal }). Con el mismo orderId no gasta otro uso: podés reintentar.
  • Mandar el código en la orden (coupon_codes), tal como lo emitió Zent: así esa venta se le atribuye. Una orden cobrada con un código de Zent cuenta como canje aunque no hayas llamado a redeem.
  • El link del carrito abandonado trae el cupón en ?zent_coupon=CÓDIGO: aplicalo solo al abrir el carrito.
  • Activar, una vez, cuando todo lo anterior ya anda: zent.coupons.enable().
checkout
// Cuando el comprador escribe un código:
const v = await zent.coupons.validate(code, { subtotal: cart.subtotal });
if (!v.valid) return showError(v.message); // «Ese cupón venció.», «Ese cupón ya se usó.»…
applyDiscount(v.discount); // envío gratis: v.coupon.type === 'free_shipping'

// Al crear la orden (idempotente por orderId):
await zent.coupons.redeem(code, { orderId: order.id, subtotal: cart.subtotal });

// Una vez, cuando todo esto ya anda en producción:
await zent.coupons.enable();
CódigoQué pasó y cómo se arregla
not_foundEse cupón no existe. No es un cupón de Zent: si tu tienda tiene cupones propios, probalo contra los tuyos.
disabledEse cupón ya no está activo. Zent lo dio de baja (por ejemplo, se apagó el beneficio).
not_startedEse cupón todavía no está vigente. Todavía no llegó su fecha de inicio.
expiredEse cupón venció. Pasó su fecha de fin (los días son de la hora de Argentina).
used_upEse cupón ya se usó. Ya se usó todas las veces que permite.
min_purchaseEste cupón pide una compra mínima. El subtotal no llega al mínimo: viene en min_purchase para mostrárselo.

Pop-up y vistas de producto

El pop-up de captación suma suscriptos con su cupón de bienvenida, y el mismo script registra qué productos mira cada contacto que llega desde un mail de Zent: con eso sale el Abandono de navegación. Se activa en Zent › Add-ons (pide los cupones activos) y se instala pegando un script en todas las páginas de tu tienda:

todas las páginas
<script src="https://usezent.xyz/zent-capture.js" data-zent-store="<id de tu tienda en Zent>" defer></script>

En la página de un producto, decile cuál es con una meta (el mismo id que mandás en Productos). Si tu sitio cambia de producto sin recargar la página, llamá a Zent.viewProduct(id) en cada cambio.

página de producto
<meta name="zent:product" content="remera-basica">

// Si tu sitio cambia de producto sin recargar:
Zent.viewProduct('remera-basica');

Cross-sell en el checkout

Zent elige el producto que mejor acompaña el carrito (el mismo motor que en Tiendanube) y tu checkout lo muestra. Corre en el navegador, sin clave: @usezent/connect/storefront habla con los ids de tu tienda. Se activa en Zent › Add-ons.

checkout
import { ZentStorefront } from '@usezent/connect/storefront';

const zent = new ZentStorefront({ store: '<id de tu tienda en Zent>' });
const { offer } = await zent.offer({
  cartId: cart.id,
  subtotal: cart.subtotal,
  items: cart.items.map((i) => ({ product_id: i.productId })), // tus ids
});

if (offer) {
  render(offer.items); // productId y variants[].id ya son los de tu tienda
  zent.offerEvent({ offerId: offer.id, type: 'shown' });
}

// Al sumarlo:
await addToCart({ productId, variantId, quantity: 1, zent_offer: offer.id });
zent.offerEvent({ offerId: offer.id, type: 'added', productId, variantId, quantity: 1 });
  • La línea que suma el producto lleva zent_offer: offer.id, y la orden la manda igual (items[].zent_offer): así esa venta se le atribuye a Zent.
  • Los eventos (shown, added, dismissed, completed) sólo ajustan cómo aprende: la venta sale de la orden cobrada.
  • Si activás el beneficio por monto, la respuesta trae benefit; al llegar al objetivo, claimBenefit da un cupón de un solo uso para ese carrito.

Conectar y chequeos

zent.connect() (o npx @usezent/connect check, que sale con código 1 si hay errores) mira lo cargado en conjunto, lo que un registro solo no muestra. Las reglas entre órdenes se miden sobre las últimas 5.000. Un error frena la conexión; un aviso conecta y te dice qué no va a funcionar. Ya conectada, revisar de nuevo nunca la desconecta.

terminal
ZENT_API_KEY=zk_live_… npx @usezent/connect check

✗ No pasó los chequeos: la tienda no se conectó.
Miramos: products 412 · customers 9310 · orders 22071 · checkouts 0 (las reglas entre órdenes, sobre las últimas 5000)

Errores (arreglalos para conectar):
  [items_unknown_products] Órdenes con productos que no llegaron: 1204 de 9877 líneas (12,2 %) apuntan a un producto o una variante que no llegó (1204 por el producto, 0 por la variante).
    → Hay líneas cuyo product_id (o variant_id) no está en lo que mandaste en Productos. Usá los mismos ids en los dos lados y cargá también los productos que ya no vendés.
    https://usezent.xyz/desarrolladores#chequeos-items_unknown_products
CódigoQué pasó y cómo se arregla
no_productsfrena la conexiónNo llegó ningún producto. Cargá el catálogo (products) antes de conectar: sin productos no hay nada que recomendar.
items_unknown_productsfrena la conexiónÓrdenes con productos que no llegaron. Hay líneas cuyo product_id (o variant_id) no está en lo que mandaste en Productos. Usá los mismos ids en los dos lados y cargá también los productos que ya no vendés.
no_paid_ordersfrena la conexiónHay órdenes pero ninguna está cobrada. Revisá cómo traducís el estado del pago: una venta cobrada es payment_status = paid.
subtotal_mismatchfrena la conexiónEl subtotal no coincide con las líneas. En muchas órdenes subtotal ≠ Σ (unit_price × quantity). subtotal es la suma de las líneas antes de descuentos y envío.
no_customersavisoSin clientes. Zent arranca en frío: no hay a quién escribirle hasta que lleguen clientes nuevos.
no_ordersavisoSin órdenes. Sin historia de compras, Zent no puede armar las etapas de tus clientes. Cargá las órdenes viejas si las tenés.
orders_unknown_customersavisoÓrdenes de clientes que no llegaron. Hay órdenes con customer.id que no está en Clientes. Cargá todos los clientes o mandá sólo el mail.
total_mismatchavisoEl total no cierra. En muchas órdenes total ≠ subtotal − descuentos + envío. Si sumás impuestos aparte está bien; si no, revisá los descuentos.
orders_without_emailavisoÓrdenes sin mail. Sin el mail del comprador esas ventas no se pueden atribuir. Mandá customer.email.
no_checkoutsavisoSin carritos abandonados. La campaña de Carrito abandonado no va a salir hasta que mandes checkouts.
coupons_not_enabledavisoLos cupones de Zent no están activos. Cuando tu checkout ya valide los cupones de Zent (zent.coupons.validate / redeem), llamá una vez zent.coupons.enable(). Hasta entonces los mails y el pop-up salen sin cupón.
domain_not_sendingavisoTu dominio de envío todavía no está verificado. La tienda conecta, pero los mails de una tienda propia salen sólo desde su dominio: verificalo en Zent › Dominios.

Errores

Un lote es todo o nada: con un registro mal no se escribe ninguno, y la respuesta trae todos los errores. Cada uno con un code estable, dónde está (path), el id del registro, cómo arreglarlo y el link a esta página. Nunca repite un mail, un nombre ni un teléfono.

HTTP/1.1 422
{
  "ok": false,
  "errors": [
    {
      "code": "invalid_enum",
      "path": "data[3].payment_status",
      "record_id": "A-20934",
      "message": "«payment_status» tiene que ser uno de: paid, pending, authorized, partially_refunded, refunded, voided (recibimos completed).",
      "hint": "Traducí tu valor al vocabulario de Zent (la tabla de valores está en la documentación).",
      "docs": "https://usezent.xyz/desarrolladores#ordenes-payment_status"
    }
  ]
}
con el SDK
import { ZentValidationError } from '@usezent/connect';

try {
  await zent.orders.upsert(orders);
} catch (e) {
  if (e instanceof ZentValidationError) console.error(e.toString()); // o e.errors
  else throw e;
}
CódigoQué pasó y cómo se arregla
requiredFalta un campo obligatorio. Mandá el campo con un valor. Revisá en la tabla del recurso cuáles son obligatorios.
required_keyFalta un campo que tenés que declarar. Este campo puede ser null, pero tiene que venir: es un dato que Zent no puede suponer por vos.
unknown_fieldCampo desconocido. Los campos van en snake_case y con los nombres de la documentación. Un campo de más casi siempre es un typo.
invalid_typeTipo incorrecto. Revisá el tipo del campo en la tabla del recurso.
invalid_enumValor no permitido. Traducí tu valor al vocabulario de Zent (la tabla de valores está en la documentación).
invalid_idId inválido. Un id es un texto o un número entero, sin espacios en los bordes y de hasta 64 caracteres.
invalid_emailMail inválido. Mandá el mail completo (nombre@dominio.com) o null si no lo tenés.
invalid_urlURL inválida. Mandá una URL absoluta que empiece con https:// (o http://).
url_not_httpsLa URL tiene que ser https. Las fotos de los mails sólo se ven con https.
url_outside_storeURL fuera del dominio de tu tienda. Los mails sólo llevan a tu tienda: la URL tiene que ser del dominio que cargaste al crear la tienda en Zent (o de un subdominio).
invalid_datetimeFecha inválida. Mandá la fecha en ISO 8601 con zona horaria, por ejemplo 2026-11-02T14:30:00-03:00 o 2026-11-02T17:30:00Z.
datetime_without_timezoneFecha sin zona horaria. Sin zona horaria la hora es ambigua: agregá el offset (-03:00) o la Z de UTC.
datetime_in_futureFecha en el futuro. Casi siempre es la zona horaria mal convertida o milisegundos donde van segundos.
not_integerTiene que ser un número entero. Sin decimales.
out_of_rangeNúmero fuera de rango. Revisá el mínimo del campo (los montos y las cantidades no pueden ser negativos).
too_longTexto demasiado largo. Recortalo al máximo del campo.
too_few_itemsFaltan elementos en la lista. La lista tiene un mínimo de elementos.
too_many_itemsDemasiados elementos en la lista. La lista tiene un máximo de elementos.
duplicate_idId repetido. Dentro de un mismo envío (o de una lista) cada id va una sola vez.
invalid_currencyMoneda inválida. Mandá el código ISO 4217 de tres letras en mayúsculas (ARS, USD, MXN…).
currency_mismatchOtra moneda que la de tu tienda. Zent mide en la moneda de tu tienda. Si vendés en varias, mandá los montos convertidos a ésa.
paid_without_paid_atOrden cobrada sin fecha de cobro. Con payment_status = paid, mandá paid_at: el mail de Post-compra se programa desde ahí.
paid_before_createdCobro muy anterior a la compra. paid_at queda más de un día antes que created_at: casi seguro que están cruzados.
id_collisionDos ids distintos chocan. Dos ids de productos o variantes distintos quedaron con el mismo número interno (pasa una vez en millones). Avisanos desde el panel de Zent (el botón de comentarios) con los dos ids.
invalid_jsonEl cuerpo no es JSON. Mandá Content-Type: application/json y un cuerpo JSON válido.
invalid_bodyCuerpo con otra forma. El cuerpo es { "data": [ …registros… ] } (y, si querés, "validate_only": true).
empty_batchEnvío vacío. `data` tiene que traer al menos un registro.
batch_too_largeDemasiados registros. Mandá hasta 500 registros por pedido (el SDK los parte solo).
payload_too_largeCuerpo demasiado grande. Mandá menos registros por pedido.
unauthorizedClave inválida. Mandá el header Authorization: Bearer zk_live_… con la clave de tu tienda (en Zent: el panel de la conexión). Una clave regenerada deja de valer la anterior.
rate_limitedDemasiados pedidos. Esperá los segundos de Retry-After y reintentá (el SDK lo hace solo).
not_foundRuta inexistente. Revisá la URL: las rutas son /api/connect/v1/<recurso>.
server_errorError de Zent. No es tu integración: reintentá en unos segundos. Si sigue, avisanos desde el panel de Zent (el botón de comentarios).

Límites

  • Hasta 500 registros y 4 MB por pedido (el SDK parte solo).
  • Hasta 240 pedidos por minuto por tienda: una carga de 100.000 órdenes son 200 pedidos. Pasado eso, 429 con Retry-After (el SDK espera y reintenta).
  • Las fechas van en ISO 8601 con zona horaria y nunca más de un día en el futuro.
  • Montos en la moneda de tu tienda en Zent, ≥ 0.

Borrar un cliente

Si un cliente te pide borrar sus datos, pedíselo también a Zent: sus órdenes quedan sin datos personales, el cliente se borra y su mail no vuelve a recibir nada.

await zent.customers.redact({ ids: ['1042'] });
// o por mail: await zent.customers.redact({ emails: ['ana@ejemplo.com'] });

API HTTP

El SDK es una capa fina: lo mismo se hace con cualquier cliente HTTP.

  • POST /api/connect/v1/products · customers · orders · checkouts: cuerpo { "data": [...] }; con "validate_only": true valida sin escribir.
  • POST /api/connect/v1/connect: los chequeos (y conectar).
  • GET /api/connect/v1/status: la tienda, cuánto llegó, el último reporte y el dominio de envío.
  • POST /api/connect/v1/redact: { "customer_ids": [...], "emails": [...] }.
  • POST /api/connect/v1/coupons/validate · redeem · enable: { "code", "subtotal", "order_id" } (Cupones).
  • POST /api/connect/v1/storefront/offer · event: desde el navegador, sin clave, con "store" (Cross-sell).
curl
curl https://usezent.xyz/api/connect/v1/orders \
  -H "Authorization: Bearer $ZENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": [ { "id": "A-20931", "...": "..." } ] }'

Con un agente de IA

Si la integración la escribe un agente (Claude Code, Cursor, Codex), el paquete trae AGENTS.md con las reglas: leer el código y no los datos de tus clientes, el permiso de marketing explícito, cómo traducir los estados y dónde enganchar el tiempo real. Pegale esto:

pedido al agente
Conectá esta tienda con Zent usando @usezent/connect. Seguí al pie de la letra
node_modules/@usezent/connect/AGENTS.md y https://usezent.xyz/desarrolladores:
mapeos puros por recurso, un script de carga inicial y los enganches de tiempo real.
No leas datos reales de clientes. Terminá cuando `npx @usezent/connect check` dé ✓.