Vesvir APIv1 · Guía para tu área de TI

Conecta tu sistema con Vesvir

Vesvir atiende, cotiza y cobra por WhatsApp con un asistente de IA y tu equipo de ventas. Esta API mantiene a Vesvir al día con tu ERP, tu inventario o tu CRM, y le regresa a tu sistema lo que se vende.

DirecciónQué viajaPara qué
Tu sistema → VesvirProductos, precios, existencias por sucursalEl asistente cotiza con tu precio y tu existencia reales.
Tu sistema → VesvirClientes y su vendedor asignado (cartera), créditoCada cliente llega con su vendedor; el asistente sabe quién es.
Vesvir → tu sistemaCotizaciones y pagos (aviso al instante + consulta)Das de alta el pedido o la factura en tu ERP sin capturar nada.
Tu sistema → VesvirEstado del pedido (facturada, surtida, pagada)El vendedor y el asistente ven en qué va cada venta.

URL base de todas las llamadas:

https://api.vesvir.com/functions/v1/vesvir-api/v1

Llave y autenticación

La llave la genera el administrador del negocio desde Vesvir (o la pide al equipo de Vesvir). Se ve una sola vez: guárdala en tu bóveda de secretos. Mándala en cada llamada:

Authorization: Bearer vsv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Cada llave tiene permisos por recurso (productos, clientes, cotizaciones, pagos, webhooks, mapeos). Si una llave se filtra, desactívala desde el panel y genera otra; las demás siguen funcionando.

GET/ping

curl https://api.vesvir.com/functions/v1/vesvir-api/v1/ping \
  -H "Authorization: Bearer $VESVIR_LLAVE"
# → {"ok":true,"negocio":"Refaccionaria Ejemplo","permisos":["productos",…],"version":"v1"}

Reglas generales

Checklist de conexión (≈1 día de TI)

  1. Pide la llave al administrador y prueba GET /ping.
  2. Manda 10 productos con ?dry_run=1, revisa errores y luego sin dry_run.
  3. Programa la sincronización de productos: completa una vez al día y, si puedes, cada vez que cambie un precio o una existencia.
  4. Carga tu cartera de clientes con su vendedor (PUT /clientes).
  5. Registra tu webhook (POST /webhooks), guarda el secreto y pruébalo con POST /webhooks/{id}/probar.
  6. Al recibir cotizacion.creada o pago.recibido, da de alta el pedido en tu ERP y regresa el estado con PATCH /cotizaciones/{folio}.
¿Tu sistema no puede adaptarse a nuestro formato? Usa el mapeo: nos mandas tus datos tal cual y Vesvir los traduce.

Productos y existencias

PUT/productos

Da de alta o actualiza por sku. Lo que ya existe conserva lo que Vesvir le agregó (fotos, notas).

CampoTipoDescripción
sku *textoTu clave única del producto.
nombre *textoComo lo verá el cliente.
precionúmeroPrecio de venta. Acepta "1,250.50".
monedatextoMXN por defecto.
existenciasobjetoPor sucursal: {"Monterrey": 5, "Saltillo": 0}. La existencia total se calcula sola.
existencianúmeroTotal, si no manejas sucursales.
disponiblesí/noSi no mandas existencias.
marca, categoria, subcategoria, unidadtextoAyudan al asistente a encontrar el producto.
descripciontextoFicha técnica corta.
codigo_fabricante, codigo_proveedortextoEl cliente puede buscar por estas claves.
listas_precioobjeto{"mayoreo": 980, "distribuidor": 900}
activosí/nofalse deja de ofrecerse.
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/productos \
  -H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
  -d '{"productos":[
        {"sku":"BAL-001","nombre":"Balata delantera Brembo Versa 2015","precio":1250.50,
         "marca":"Brembo","existencias":{"Monterrey":5,"Saltillo":0}},
        {"sku":"FIL-10","nombre":"Filtro de aceite","precio":95,"disponible":true}
      ]}'
# → {"ok":true,"creados":2,"actualizados":0,"con_error":0,"errores":[]}

GET/productos?sku=BAL-001,FIL-10&desde=2026-10-01&limite=100&pagina=1

POST/productos/desactivar {"skus":["KIT-OLD"]}

Clientes y cartera

PUT/clientes

Por id_externo (tu clave de cliente) o rfc. Con vendedor, el cliente pasa directo con esa persona cuando escribe.

CampoTipoDescripción
id_externo * (o rfc)textoTu clave del cliente.
nombre *textoRazón social o nombre.
rfctextoSe valida el formato.
telefonos, correoslistaCon ellos Vesvir lo reconoce cuando escribe por WhatsApp.
vendedortextoCorreo o nombre del vendedor en Vesvir, o su clave de vendedor.
segmento, lista_precio, descuento_pctCondiciones comerciales.
creditoobjeto{"limite":50000,"dias":30,"saldo":12000}
ultima_comprafechaAAAA-MM-DD. Si tu cuenta tiene la regla "un cliente que no compra desde hace N meses se atiende como nuevo", el cliente con fecha más vieja llega al vendedor en turno en vez de a su ejecutivo, y lo recupera cuando vuelve a comprar.
activosí/no
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/clientes \
  -H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
  -d '{"clientes":[{"id_externo":"C-100","nombre":"Taller El Rayo SA de CV","rfc":"TER010101AB1",
        "telefonos":["8110000001"],"vendedor":"mario@tuempresa.mx",
        "credito":{"limite":50000,"dias":30}}]}'
# → {"ok":true,"creados":1,"actualizados":0,"vendedores_no_encontrados":[]}

GET/clientes?id_externo=C-100&rfc=&desde=&limite=&pagina=

Vendedores (equipo sin acceso al panel)

PUT/vendedores

Da de alta a tus vendedores tal como están en tu sistema, sin que entren al panel: así cada cliente llega con su vendedor y a él le llega el aviso al celular o al correo cuando el asistente lo atiende o le cotiza. No ocupan usuario de tu plan ni entran a la rotación de clientes nuevos. Cuando quieras darle acceso a alguien, lo invitas desde Equipo.

CampoTipoDescripción
id_externo *textoTu clave del vendedor. Es la misma que mandas en clientes[].vendedor. Si la misma persona tiene varias claves con el mismo correo, queda un solo vendedor con todas sus claves.
nombre *texto
correotextoPara el aviso por correo. Si no tiene, se da de alta igual (sin correo).
telefonotextoCelular para el aviso por WhatsApp; se queda con 10 dígitos (acepta 52, extensiones y varios números: toma el primero).
puesto, sucursal, ciudad, estadotextoSolo informativos.
activosí/noDesactiva solo a los vendedores que dio de alta la API.
curl -X PUT https://api.vesvir.com/functions/v1/vesvir-api/v1/vendedores \
  -H "Authorization: Bearer $VESVIR_LLAVE" -H "Content-Type: application/json" \
  -d '{"vendedores":[{"id_externo":"V-57","nombre":"Mario Pérez","correo":"mario@tuempresa.mx",
        "telefono":"81 1234 5678","puesto":"Vendedor","sucursal":"Monterrey"}]}'
# → {"ok":true,"creados":1,"actualizados":0,"con_celular":1,"con_correo":1}

GET/vendedores

Tu propio formato (mapeo)

Si tu sistema ya tiene su formato (SAP Business One, Odoo, Microsip, un JSON propio), no lo cambies: Vesvir lo traduce.

  1. POST/mapeos/productos/sugerir con {"ejemplo": [ …uno o tres registros tal como los manda tu sistema… ]}. Vesvir propone el mapeo y te enseña cómo quedaría (vista_previa).
  2. Revísalo y guárdalo: PUT/mapeos/productos con {"mapa": {…}}.
  3. Desde ahí manda tus datos tal cual: POST/ingesta/productos con tu lista.
# ejemplo con SAP Business One
{"mapa":{
  "sku":"ItemCode", "nombre":"ItemName",
  "precio":{"ruta":"ItemPrices.PriceList1.Price","tipo":"numero"},
  "marca":"Manufacturer", "categoria":"ItemsGroupName", "unidad":"InventoryUOM",
  "activo":{"ruta":"Valid","tipo":"booleano"},
  "existencias":{"ruta":"ItemWarehouseInfoCollection","clave":"WarehouseCode","valor":"InStock"}
}}

Igual con clientes: /mapeos/clientes/sugerir, /mapeos/clientes, /ingesta/clientes. Los sí/no se entienden en cualquier forma (true, 1, "Sí", "activo", "tYES").

Cotizaciones

Las crea el asistente o el vendedor en Vesvir, con tus precios. Las recibes al instante por webhook o las consultas:

GET/cotizaciones?desde=2026-10-01&estado=enviada,aceptada

GET/cotizaciones/{folio}

{"ok":true,"cotizacion":{"folio":"COT-2026-0001","estado":"enviada",
  "cliente":{"nombre":"Taller El Rayo SA de CV","telefono":"528110000001","rfc":"TER010101AB1"},
  "conceptos":[{"sku":"BAL-001","descripcion":"Balata delantera…","cantidad":2,"precio_unitario":1250.5,"importe":2501}],
  "subtotal":2501,"iva":400.16,"total":2901.16,"moneda":"MXN","vigencia":"2026-10-11","pdf":"https://…"}}

PATCH/cotizaciones/{folio}

Regresa lo que pasó en tu ERP. estado: aceptada, facturada, surtida, enviada_cliente, entregada, cancelada, pagada.

{"estado":"facturada","factura":{"folio":"F-5501","uuid":"1A2B3C4D-…"},"notas":"Sale mañana por paquetería"}

Pagos

Si el pago llega por fuera (transferencia a tu cuenta, efectivo en mostrador), confírmalo y Vesvir hace lo demás: le avisa al cliente, marca la venta como ganada y cierra el seguimiento.

POST/pagos

{"folio":"COT-2026-0001","monto":2901.16,"referencia":"SPEI 123456","metodo":"transferencia"}

Avisos (webhooks)

Vesvir le avisa a tu sistema en cuanto pasa algo. Registra una URL https:

POST/webhooks {"url":"https://erp.tuempresa.mx/vesvir","eventos":["cotizacion.creada","pago.recibido"]}

La respuesta trae el secreto (whsec_…) con el que verificas la firma. Se ve una sola vez.

EventoCuándo
cotizacion.creadaEl asistente o un vendedor manda una cotización.
cotizacion.actualizadaCambia de estado (aceptada, pagada, cancelada…). Trae estado_anterior.
pago.recibidoSe confirma un pago (link, SPEI con conciliación, comprobante validado o tu POST /pagos).

Cada aviso es un POST con este cuerpo:

{"id":"0b6e…","tipo":"cotizacion.creada","creado":"2026-10-04T05:22:10Z",
 "datos":{"folio":"COT-2026-0001","estado":"enviada","total":2901.16,"cliente":{…},"conceptos":[…],"origen":"vesvir"}}

Verificar la firma

Cada aviso trae X-Vesvir-Firma: t=<segundos>,v1=<hex>, un HMAC-SHA256 de "<t>.<cuerpo crudo>" con tu secreto. Rechaza avisos con firma inválida o con t de hace más de 5 minutos.

Node.js

import crypto from "node:crypto";
function firmaValida(cuerpoCrudo, encabezado, secreto) {
  const { t, v1 } = Object.fromEntries(encabezado.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const esperado = crypto.createHmac("sha256", secreto).update(`${t}.${cuerpoCrudo}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}

Python

import hmac, hashlib, time
def firma_valida(cuerpo_crudo: bytes, encabezado: str, secreto: str) -> bool:
    partes = dict(p.split("=", 1) for p in encabezado.split(","))
    if abs(time.time() - int(partes["t"])) > 300:
        return False
    esperado = hmac.new(secreto.encode(), f'{partes["t"]}.'.encode() + cuerpo_crudo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, partes["v1"])

C# (.NET)

static bool FirmaValida(string cuerpo, string encabezado, string secreto) {
  var p = encabezado.Split(',').Select(x => x.Split('=', 2)).ToDictionary(x => x[0], x => x[1]);
  if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(p["t"])) > 300) return false;
  using var h = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secreto));
  var esperado = Convert.ToHexString(h.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{p["t"]}.{cuerpo}"))).ToLower();
  return System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
    System.Text.Encoding.UTF8.GetBytes(esperado), System.Text.Encoding.UTF8.GetBytes(p["v1"]));
}

Errores

Siempre {"ok":false,"error":{"codigo":"…","mensaje":"…"}}. En lotes, los registros con problema vienen en errores con su posición (i) y los demás sí se guardan.

HTTPcodigoQué hacer
400json_invalido, sin_datos, estado_invalidoRevisa el cuerpo contra esta guía.
401sin_llave, llave_invalidaRevisa el encabezado Authorization o genera otra llave.
403sin_permisoLa llave no tiene permiso para ese recurso.
404no_existe, ruta_desconocidaFolio o ruta incorrectos.
409sin_mapeoGuarda tu mapeo antes de usar /ingesta.
413lote_grandeDivide en lotes de 1,000.
429demasiadas_llamadasEspera y reintenta (120 por minuto).
500/502error_internoReintenta; si sigue, avísanos con la hora de la llamada.

Soporte

Escríbele a tu contacto en Vesvir (o a Buddy por WhatsApp) con el nombre del negocio y la hora de la llamada. Cada llamada queda en la bitácora, así que vemos exactamente qué llegó. ¿Tu ERP necesita un conector a la medida (Microsip, Aspel, CONTPAQi con base de datos local)? Pídelo: lo armamos como proyecto Enterprise.