Vendly Documentación API v1

Introducción

La Vendly API v1 te permite integrar el catálogo, pedidos, clientes y métricas de tu tienda con cualquier aplicación externa: e-commerce, app móvil, ERP, chatbots, y más.

Base URL: https://appvendly.lat/api/v1/

¿Qué parte de la API uso?

Quiero…Uso¿Dónde va el código?
Mostrar mi catálogo y recibir pedidos en mi página web Tienda web — /api/public/… sin clave Puede ir en el navegador (HTML / JavaScript de tu web)
Ver clientes, ventas, métricas o stock; crear pedidos con datos del cliente, variantes o precio mayorista; cambiar el estado de pedidos; sincronizar con otro sistema API v1 — /api/v1/… con API Key Solo en tu servidor (backend). Nunca en la página web.
La API Key nunca va en tu página web. Leé la sección Seguridad antes de integrar.
Soporte: WhatsApp +595 971 994 032  ·  appvendly.lat

Cómo obtener tu API Key

Las API Keys son generadas por el equipo de Vendly y entregadas al cliente al contratar la integración.
El cliente no puede generarlas desde su propio panel.

Pasos para obtener tu key

  1. Solicitá tu API Key por WhatsApp 0971 994 032 o desde appvendly.lat
  2. El equipo la genera desde el panel administrativo y te la entrega
  3. Guardala como una contraseña: solo en tu servidor, en una variable de entorno (por ejemplo VENDLY_API_KEY). No se puede recuperar si se pierde, solo regenerar.
  4. Tu servidor la manda en el header X-API-Key de cada request a /api/v1

Características de la key

Contacto

WhatsApp 0971 994 032 appvendly.lat

Seguridad: cómo proteger tu API Key

Tu API Key es como la contraseña de tu negocio.

Con ella se pueden ver todos tus clientes (nombre, teléfono, email, RUC/CI, dirección), tus ventas y métricas, crear pedidos que descuentan stock y cancelar pedidos. Quien la tenga puede hacerlo desde cualquier computadora.

Por qué no puede ir en tu página web

Todo lo que forma parte de una página web (HTML, archivos .js, configuración de React/Vue/Next, etc.) se descarga en el navegador de cada visitante. Cualquiera puede verlo con clic derecho → Ver código fuente o con la tecla F12. Si la clave está ahí — aunque esté "escondida" en un archivo, minificada o en una variable — es pública.

✗ Nunca

  • Poner la clave en el HTML o en archivos .js de tu web
  • Variables públicas de frameworks (NEXT_PUBLIC_…, VITE_…, REACT_APP_…): terminan en el navegador
  • Llamar a /api/v1 desde el navegador
  • Subir la clave a GitHub u otro repositorio
  • Mandarla en la URL (?api_key=…): queda en historiales y registros

✓ Siempre

  • Guardar la clave en una variable de entorno de tu servidor (VENDLY_API_KEY)
  • Llamar a /api/v1 solo desde tu backend (Node, PHP, Python, Laravel, una función serverless, etc.)
  • Para mostrar productos y recibir pedidos en tu web, usar la Tienda web sin clave
  • Si tu web necesita datos privados, que llame a tu servidor y tu servidor llame a Vendly

Cómo se conecta todo

Tu página web (navegador del cliente)
   │
   ├── Catálogo y pedido simple ──────────► appvendly.lat/api/public/{tenant_id}/…   (sin clave)
   │
   └── Pedido con datos del cliente ──────► TU servidor ──► appvendly.lat/api/v1/pedidos
       / variantes / mayorista                (tiene la clave      X-API-Key: process.env.VENDLY_API_KEY
                                               en variable de entorno)

Ejemplo: tu servidor como intermediario (Node.js + Express)

// .env de TU servidor (nunca se sube al repositorio ni a la web)
VENDLY_API_KEY=tu-clave-de-64-caracteres

// server.js de TU servidor
app.post('/checkout', async (req, res) => {
  // 1. Tu web le manda a TU servidor los productos elegidos y los datos del cliente
  const { items, customer } = req.body;
  // 2. Tu servidor crea el pedido en Vendly con la clave (el navegador nunca la ve)
  const r = await fetch('https://appvendly.lat/api/v1/pedidos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.VENDLY_API_KEY },
    body: JSON.stringify({ items, customer })
  });
  // 3. A la web solo se le devuelve lo necesario
  const { ok, pedido, error } = await r.json();
  res.status(ok ? 200 : 400).json(ok ? { ticket: pedido.ticket_number, total: pedido.total } : { error });
});

Si tu clave quedó expuesta

  1. Avisanos por WhatsApp: la desactivamos al momento y te generamos una nueva.
  2. Sacala del código de tu web y del repositorio.
  3. Configurá la nueva solo en las variables de entorno de tu servidor.

Autenticación

Todos los endpoints /api/v1 requieren el header X-API-Key con tu clave activa. Estas llamadas se hacen solo desde tu servidor.

Los ejemplos de esta documentación son para tu servidor (Node.js). La clave se lee de una variable de entorno: process.env.VENDLY_API_KEY. No copies estos ejemplos en el código de tu página web.

Header requerido

X-API-Key: <tu-clave-de-64-caracteres>

Ejemplo Node.js (en tu servidor)

const res = await fetch('https://appvendly.lat/api/v1/metricas', {
  headers: { 'X-API-Key': process.env.VENDLY_API_KEY }
});
const data = await res.json();
console.log(data);

Ejemplo cURL (terminal / servidor)

curl -H "X-API-Key: $VENDLY_API_KEY" https://appvendly.lat/api/v1/metricas

Códigos de error

HTTPSignificadoCausa frecuente
200OKSolicitud exitosa
400Bad RequestFaltan campos obligatorios o datos inválidos
401UnauthorizedHeader X-API-Key ausente, inválido o inactivo
404Not FoundRecurso no encontrado
500Server ErrorError interno — reportar al soporte

Formato de error

{ "ok": false, "error": "Descripción del error" }
GET /api/v1/catalogo/:id Detalle de un producto con todas sus variantes

Node.js (en tu servidor)

const res = await fetch(`https://appvendly.lat/api/v1/catalogo/${productId}`, {
  headers: { 'X-API-Key': process.env.VENDLY_API_KEY }
});
const { producto } = await res.json();

Respuesta

{ "ok": true, "producto": { /* mismo esquema que el array de /catalogo */ } }
GET /api/v1/stock Stock actual de todos los productos activos

Parámetros query

ParámetroTipoDescripción
bajobooleanOpcional true para devolver solo productos con stock ≤ mínimo

Respuesta

{
  "ok": true,
  "stock": [
    { "id": "uuid", "name": "Producto", "code": "P001",
      "category": "Bazar", "stock": 3, "min_stock": 5, "unit": "Unid.", "active": true }
  ]
}

Pedidos

GET /api/v1/pedidos Lista de pedidos del canal web (últimos 200)

Respuesta

{
  "ok": true,
  "pedidos": [
    {
      "id": "uuid", "ticket_number": "WEB-123456",
      "total": 120000, "estado": "pendiente",   // pendiente | pagado | cancelado
      "status": "pending",
      "payment_method": "efectivo",
      "cliente": "Juan Pérez", "cliente_phone": "0981000000",   // "Consumidor Final" si no hay cliente
      "customer_note": "Entregar por la tarde",
      "created_at": "2026-07-05T10:30:00Z"
    }
  ]
}
GET /api/v1/pedidos/:id Detalle de un pedido web con sus ítems

Respuesta

{
  "ok": true,
  "pedido": {
    "id": "uuid", "ticket_number": "WEB-123456", "total": 90000,
    "estado": "pendiente", "payment_method": "transferencia", "cliente": "Juan Pérez",
    "items": [
      { "product_id": "uuid", "product_name": "Remera — Negro", "variant_id": "uuid-variante",
        "variante": "Negro", "quantity": 2, "price": 45000, "subtotal": 90000 }
    ]
  }
}
POST /api/v1/pedidos Crear pedido web — descuenta stock automáticamente
El precio de cada producto y el total los calcula Vendly con el precio de venta del catálogo. Si enviás unit_price o total se ignoran. El total devuelto se redondea según la moneda (PYG sin decimales, ARS con 2).

Body JSON

CampoTipoDescripción
itemsarrayRequerido Entre 1 y 200 ítems: [{ product_id, quantity, variant_id }]. quantity mayor a 0 y hasta 10000.
items[].variant_idstringRequerido si el producto tiene variantes id de la variante (color o modelo/talle) tal como viene en /catalogo. En su lugar se puede mandar variante con el nombre exacto (ej. "Negro"). Se descuenta el stock de esa variante; la cantidad debe ser entera.
price_typestringOpcional "minorista" (default) o "mayorista". Con mayorista se usa el precio mayorista de cada producto; si un producto no lo tiene cargado, se usa su precio de venta.
payment_methodstringOpcional default: efectivo
customer_notestringOpcional Nota del cliente
customerobjectOpcional { name, phone, email }

Node.js (en tu servidor)

const res = await fetch('https://appvendly.lat/api/v1/pedidos', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': process.env.VENDLY_API_KEY
  },
  body: JSON.stringify({
    items: [
      { product_id: 'uuid-producto', quantity: 2, variant_id: 'uuid-variante' },   // producto con variantes
      { product_id: 'uuid-otro', quantity: 1 }                                  // producto sin variantes
    ],
    price_type: 'minorista',
    payment_method: 'transferencia',
    customer: { name: 'Juan Pérez', phone: '0981000000' }
  })
});
const { ok, pedido } = await res.json();   // pedido.total = total calculado por Vendly

cURL

curl -X POST https://appvendly.lat/api/v1/pedidos \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $VENDLY_API_KEY" \
  -d '{"items":[{"product_id":"uuid","quantity":1}]}'

Respuesta exitosa

{
  "ok": true,
  "pedido": {
    "id": "uuid", "ticket_number": "WEB-123456", "total": 90000,
    "estado": "pendiente", "price_type": "minorista",
    "items": [
      { "product_id": "uuid", "product_name": "Remera — Negro", "variante": "Negro",
        "quantity": 2, "price": 45000, "subtotal": 90000, "precio": "minorista" }
    ]
  }
}

Errores

HTTPerrorCausa
400Sin productositems vacío o con más de 200 ítems
400Artículo inválido en el pedidoFalta product_id o quantity no es válida
400Producto … no encontradoEl product_id no existe o está inactivo
400Stock insuficiente: "…" (disponible: N)No hay stock del producto o de la variante pedida
400El producto "…" tiene variantes: indicá variant_id o varianteFalta indicar el color o modelo
400La variante "…" no existe en "…"El variant_id o nombre no corresponde a ese producto
400price_type debe ser "minorista" o "mayorista"Valor de price_type no válido
Ante cualquier error no se crea el pedido ni se descuenta stock.
PATCH /api/v1/pedidos/:id Marcar un pedido web como pagado o cancelado
Al cancelar, Vendly devuelve el stock automáticamente: al producto, a la variante (color/modelo) o a los componentes si es un combo. Un pedido cancelado no se puede volver a pagar.

Body JSON

CampoTipoDescripción
estadostringRequerido "pagado" o "cancelado"
payment_methodstringOpcional Al marcar pagado, actualiza el método de pago (ej. "transferencia")

Node.js (en tu servidor)

const res = await fetch(`https://appvendly.lat/api/v1/pedidos/${pedidoId}`, {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.VENDLY_API_KEY },
  body: JSON.stringify({ estado: 'cancelado' })
});

Respuesta

{ "ok": true, "pedido": { "id": "uuid", "ticket_number": "WEB-123456", "estado": "cancelado", "stock_devuelto": true } }

Errores

HTTPerror
400estado debe ser "pagado" o "cancelado"
400El pedido ya está cancelado
404Pedido no encontrado (no existe, no es un pedido web o es de otro negocio)

También se puede hacer desde el panel de Vendly: en los pedidos web pendientes, Rechazar; o en cualquier venta, Anular. En ambos casos el stock vuelve solo.

Clientes

GET /api/v1/clientes Lista paginada de clientes

Parámetros query

ParámetroTipoDescripción
pageintegerOpcional Página (default: 1)
limitintegerOpcional máx. 100 (default: 50)

Respuesta

{
  "ok": true,
  "clientes": [
    { "id": "uuid", "name": "Cliente Ejemplo",
      "phone": "0981000000", "email": "c@mail.com", "ruc_ci": "1234567",
      "address": "Av. Siempre Viva 123", "created_at": "2026-07-05T10:30:00Z" }
  ],
  "total": 152, "page": 1, "limit": 50
}
GET /api/v1/clientes/:id Detalle completo de un cliente

Node.js (en tu servidor)

const res = await fetch(`https://appvendly.lat/api/v1/clientes/${clienteId}`, {
  headers: { 'X-API-Key': process.env.VENDLY_API_KEY }
});

Respuesta

{ "ok": true, "cliente": { "id": "uuid", "name": "...", "address": "...", /* ... */ } }

Ventas

GET /api/v1/ventas Historial de ventas (default: últimas 100)

Parámetros

ParámetroTipoDescripción
limitintegerOpcional máx. 500 (default: 100)

Respuesta

{
  "ok": true,
  "ventas": [
    { "id": "uuid", "ticket_number": "T-0001", "total": 85000,
      "payment_method": "efectivo", "status": "completed",
      "channel": "pos", "cliente": "Juan Pérez",
      "created_at": "2026-07-05T10:00:00Z" }
  ]
}
GET /api/v1/ventas/resumen Métricas de ventas: hoy, mes, ticket promedio

Respuesta

{
  "ok": true,
  "total_hoy": 350000,
  "ventas_hoy": 4,
  "total_mes": 8500000,
  "ventas_mes": 87,
  "ticket_promedio": 97701.15   // promedio histórico de todas las ventas
}

Métricas & Configuración

GET /api/v1/metricas KPIs generales del tenant

Respuesta

{
  "ok": true,
  "productos_activos": 48,
  "clientes_total": 152,
  "ventas_mes": 8500000,
  "ventas_mes_count": 87,
  "stock_bajo": 3
}
GET /api/v1/config Moneda del negocio y cotizaciones USD/ARS configuradas

Respuesta

{
  "ok": true,
  "config": { "currency": "PYG", "country": "PY", "usd_rate": 7800, "ars_rate": 9.5 }
}

currency es PYG para negocios de Paraguay y ARS para negocios de Argentina. Todos los montos de la API están en esa moneda.

Tienda web: catálogo y pedidos sin clave

Estos endpoints están hechos para usarse directamente desde tu página web (el navegador del visitante). No llevan API Key y solo exponen lo que ya es público: los productos publicados y la creación de pedidos. No dan acceso a clientes, ventas ni métricas.

Base URL: https://appvendly.lat/api/public/{tenant_id}/

GET /api/public/{tenant_id}/productos Productos publicados (paginado)

Parámetros query

ParámetroTipoDescripción
pageintegerOpcional Página (default: 1)
limitintegerOpcional Items por página, máx. 100 (default: 20)

JavaScript (en tu página web)

const TIENDA = 'tu-tenant-id';   // público, no es la API Key
const res = await fetch(`https://appvendly.lat/api/public/${TIENDA}/productos?limit=50`);
const { productos, total } = await res.json();

Respuesta

{
  "ok": true,
  "productos": [{
    "id": "uuid", "name": "Remera", "description": "...", "category": "Indumentaria",
    "price": 50000, "precio_minorista": 50000, "precio_mayorista": 40000, "stock": 8,
    "image_url": "https://appvendly.lat/uploads/remera.jpg", "images": ["https://…"],
    "variants": [{ "color": "Negro", "stock": 5 }]
  }],
  "total": 47, "page": 1, "limit": 50
}

También: GET /api/public/{tenant_id}/productos/{id} (un producto) y GET /api/public/{tenant_id}/categorias. Este listado incluye productos sin stock: mostralos como "Agotado" usando el campo stock.

GET /api/public/{tenant_id}/config Moneda de la tienda y cotizaciones

Respuesta

{ "ok": true, "currency": "PYG", "country": "PY", "usd_rate": 7800, "ars_rate": 9.5 }

currency: PYG (Paraguay, sin decimales) o ARS (Argentina, hasta 2 decimales).

POST /api/public/{tenant_id}/orders Registrar un pedido desde la tienda web
El precio y el total los calcula Vendly con el catálogo: lo que mande el navegador no se usa. El pedido entra como pendiente y descuenta stock.

Body JSON

CampoTipoDescripción
itemsarrayRequerido [{ product_id, quantity }], quantity mayor a 0
payment_methodstringOpcional default: transferencia
customer_notestringOpcional Nombre, teléfono, dirección y aclaraciones del cliente (texto libre)

JavaScript (en tu página web)

const res = await fetch(`https://appvendly.lat/api/public/${TIENDA}/orders`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },   // sin X-API-Key
  body: JSON.stringify({
    items: [{ product_id: 'uuid-producto', quantity: 2 }],
    payment_method: 'transferencia',
    customer_note: 'Juan Pérez · 0981 000 000 · Av. España 123, Asunción'
  })
});
const { success, order_id, error } = await res.json();

Respuesta

{ "success": true, "order_id": "uuid" }      // error: { "success": false, "error": "Producto no disponible" }
¿Necesitás registrar al cliente, elegir variante (color/talle) o usar precio mayorista? Eso lo hace POST /api/v1/pedidos, que lleva la clave: tu web le manda el pedido a tu servidor y tu servidor lo crea en Vendly (ver el ejemplo en Seguridad).