YUBASync Link
Conecta lo que ya tienes.
118 conectores publicados, una API REST con contrato OpenAPI, webhooks firmados con HMAC y autoridad por campo entre tu ERP y cada canal.
$ curl -G https://suite.yubasync.com/api/v2/items \
-H 'Authorization: Bearer $TOKEN' \
-d max=2
{
"data": [
{ "code": "ARR-500", "description": "Arroz premium 500 g",
"totalQuantity": 1284, "existenciesOnRoutes": 96, "price": 24500 },
{ "code": "PAN-024", "description": "Panela cuadrada x24",
"totalQuantity": 340, "existenciesOnRoutes": 0, "price": 18900 }
],
"pagination": { "page": 1, "pageSize": 2, "totalPages": 1642, "totalRecords": 3283 }
}
El que ya usas probablemente está.
Esta es la lista completa de conectores publicados, no una selección de logos. Búscalo por nombre o filtra por tipo. Si el tuyo no aparece, no está: preferimos decirlo antes de la reunión.
Los conectores marcados con un país tienen lógica fiscal o de negocio propia de ese país; los marcados «global» funcionan igual en cualquiera. Ninguno se cobra aparte.
Una API que puedes leer antes de llamarnos.
REST sobre HTTPS, JSON en los dos sentidos, token Bearer y un OpenAPI público que puedes cargar en tu propio Swagger. El identificador de empresa viaja firmado dentro del token: no hay ningún parámetro de compañía que un cliente pueda manipular.
$ curl -X POST https://suite.yubasync.com/api/login/authenticate \
-H 'Content-Type: application/json' \
-d '{"username":"acme_prod","password":"••••••••••"}'
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXAiOiJiYXQtYXBpIiwi…"
}
# El token dura 90 días; renovarlo es volver a llamar este endpoint.
# Máximo 10 intentos por minuto. Usuario inexistente y clave incorrecta
# devuelven exactamente la misma respuesta — a propósito.
# Revocar la credencial corta el acceso YA, sin esperar al vencimiento.
$ curl -G https://suite.yubasync.com/api/v2/salesOrder \
-H 'Authorization: Bearer $TOKEN' \
-d page=1 -d max=100 \
-d start=01/05/2026 -d end=31/05/2026 \
-d filterWithDate=lastUpdated
{
"data": [
{
"id": "SO-2026-004182",
"customer": "900123456",
"type": "Wholesale order",
"dateCreated": "2026-05-14T09:22:31.000Z",
"scheduledDateForDelivery": "2026-05-16T00:00:00.000Z",
"priceList": "MAY-01",
"totalBeforeTax": 1840000,
"totalTax": 349600,
"totalSales": 2189600,
"billable": true,
"billed": false,
"exported": true,
"externalId": "P0034182",
"networkSignalQuality": "POOR",
"errorMessageFromIntegration": null,
"items": [
{ "product": "ARR-500", "quantity": 40, "price": 24500, "total": 980000 },
{ "product": "PAN-024", "quantity": 50, "price": 17200, "total": 860000 }
]
}
],
"pagination": { "page": 1, "pageSize": 100, "totalPages": 7, "totalRecords": 683 }
}
$ curl -i https://suite.yubasync.com/api/v2/items
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="bat-api"
Content-Type: application/json; charset=utf-8
Cache-Control: no-store
{
"errorCode": "INVALID_TOKEN",
"message": "Missing bearer token",
"details": "Header Authorization: Bearer {TOKEN} is required"
}
# Códigos estables: INVALID_TOKEN · TOKEN_EXPIRED · ACCESS_DENIED
# INVALID_CREDENTIALS · RATE_LIMIT_EXCEEDED
# El 429 siempre trae Retry-After: 60 — no hay que adivinar el backoff.
# `details` se omite cuando no aporta; nunca llega como null.
| Método | Ruta | Qué devuelve |
|---|---|---|
| POST | /api/login/authenticate | Emite el Bearer Token de 90 días a partir del usuario técnico. |
| GET | /api/v2/salesOrder | Pedidos de venta con sus líneas, totales, GPS y estado de exportación. |
| GET | /api/v2/customers | Clientes. Alias equivalente: /api/v2/customer. |
| GET | /api/v2/items | Existencias por producto, ya consolidadas entre bodegas y rutas. Alias: /api/v2/inventory. |
| GET | /api/v2/priceList | Listas de precios vigentes por producto. |
| GET | /api/v2/deliveryBalance | Cartera: saldos pendientes por cliente y documento. Alias: /api/v2/cartera. |
| GET | /api/v2/visits | Visitas y agenda del vendedor. Alias: /api/v2/calendarEvent. |
| GET | /api/v2/users | Usuarios de la compañía. Alias: /api/v2/user. |
| GET | /api/v2/openapi.json | El contrato OpenAPI completo. Público a propósito: se lee sin token. |
Paginación siempre explícita
Toda lista responde con data y pagination. Nunca hay que deducir el final por «me llegaron menos de los que pedí» — que falla justo cuando el total es múltiplo exacto del tamaño de página.
El tope recorta, no falla
Pedir max=5000 no devuelve un 400: se recorta a 500 y el valor realmente aplicado viene en pagination.pageSize.
Ventana incremental
start y end en dd/MM/yyyy, interpretadas en UTC-05:00. Un end sin hora se completa a las 23:59:59 para que el último día quede incluido.
La empresa va en el token
El identificador de compañía sale del JWT firmado, jamás de un parámetro. Una credencial no sirve para leer otra compañía aunque se conozca su código.
Errores con código estable
Siempre { errorCode, message }, con details sólo cuando aporta. El mensaje crudo de una excepción no sale nunca: podría filtrar nombres de tabla.
ERPs que sólo hablan por VPN
Para los sistemas legacy que no publican nada a internet hay un agente on-premise: se instala del lado del cliente y abre un puente cifrado y auditable hacia el hub.
Webhooks que puedes verificar.
Cada entrega va firmada con HMAC SHA-256 sobre el cuerpo crudo más una marca de tiempo. Tu extremo puede comprobar que el mensaje salió de nosotros y que no es la repetición de uno viejo — sin confiar en la IP de origen.
X-Yuba-Event: shipment.delivered
X-Yuba-Delivery-Id: 6f1c1a9e-4b02-4f4e-9a31-2c0e7d5a11b8
X-Yuba-Webhook-Id: 0b2d94c7-1f88-4a55-bb10-8e6f0a7c3d21
X-Yuba-Signature: t=1779023011,v1=9f3c…c1
User-Agent: YubaWebhook/1.0
{ "id": "6f1c1a9e-…", "event": "shipment.delivered",
"ts": "2026-05-18T14:23:31.482Z", "data": { … } }
// Verificación del lado del receptor — la firma se calcula sobre `${t}.${body}`
import crypto from "node:crypto";
export function verificar(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
// Anti-replay: fuera de ±300 s se descarta aunque la firma cuadre.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const esperado = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
// Comparación en tiempo constante: un `===` filtra información por timing.
return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}
Reintentos acotados
Tres intentos con espera creciente: 1 minuto, 5 minutos y 30 minutos. Después la entrega queda marcada como fallida y visible — no se reintenta para siempre contra un endpoint caído.
Identificador de entrega
Cada envío trae su X-Yuba-Delivery-Id. Si un reintento llega dos veces, tu lado lo descarta con una comprobación de una línea.
Firma sobre el cuerpo crudo
Hay que verificar antes de parsear el JSON: reserializar el cuerpo cambia los espacios y rompe la firma. Es el fallo más común al integrar webhooks firmados.
Catálogo de eventos publicables
Un evento que no esté en esta lista no se puede suscribir: el emisor valida el nombre contra el catálogo antes de encolar, así una errata de tipeo falla en el registro y no seis meses después en silencio.
La autoridad no es de un sistema: es de un campo.
Cuando el mismo producto vive en el ERP, en la tienda y en dos marketplaces, «¿quién manda?» no tiene una respuesta, tiene una por campo. El stock lo manda el ERP porque es la única verdad de existencias; el precio de venta puede mandarlo la tienda; el catálogo, un PIM distinto. Declarar eso es lo que hace que dos integraciones corriendo en paralelo no se pisen.
| Campo canónico | Dueño | Dirección | Ante un cambio ajeno |
|---|---|---|---|
stock |
El ERP | inbound |
owner-wins — se rechaza y queda en la traza |
price |
El canal | bidirectional |
last-writer — gana el más reciente |
catalog |
El PIM | outbound |
manual — entra a revisión humana |
status |
El ERP | bidirectional |
owner-wins — se rechaza y queda en la traza |
El error caro no es un fallo
Es un dato aceptado que no debía aceptarse. La tienda avisa «vendí 3» y baja el stock; el ERP, que ya lo sabía por su propio flujo, lo baja otra vez. Nada se cae y nadie se entera: simplemente se deja de vender un producto que sí había.
Por defecto, conservador
Un dueño sin declarar cae en manual (que decida una persona) y una dirección sin declarar cae en inbound (escuchar antes que empujar). Ante la duda no se pisa el dato bueno.
Decisiones auditables
Cada cambio entrante queda como aceptado, rechazado o en conflicto, con su origen y su marca de tiempo. Cuando alguien pregunte por qué el precio cambió un martes, hay una respuesta.
Evalúa la integración sin agendar una llamada.
La documentación y el contrato OpenAPI son públicos: se leen, se cargan en tu Swagger y se prueban. Si después de eso quedan preguntas, hablas con quien escribió el conector — no con un comercial.
Los conectores no se cobran por unidad ni por volumen de eventos: vienen incluidos con el producto. Ver precios.