API REST de Mindy POS

Integra tu negocio con sistemas externos. Consulta y gestiona productos, stock, ventas, egresos, reportes, CRM y más — de forma programática con autenticación por API Key. Soporta GET, POST, PUT y DELETE.

URL base

https://app.mindy.gt/api/v1/

Métodos

GET POST PUT DELETE

Formato

JSON · UTF-8

Auth

Bearer token (API Key)

Cómo comenzar

  1. Genera tu API Key

    Ve a Ajustes › API en el panel de Mindy y crea una nueva key. Se muestra una sola vez — guárdala. Formato: mk_live_xxxxxxxx.

  2. Configura los permisos

    Al crear la key elige los scopes necesarios y el límite de requests por minuto (30 – 300).

  3. Haz tu primera petición

    curl -X GET "https://app.mindy.gt/api/v1/products?per_page=1" \
      -H "Authorization: Bearer mk_live_tu_api_key"
  4. Verifica la respuesta

    {
      "success": true,
      "data": [ ... ],
      "meta": { "page": 1, "per_page": 1, "total": 150, "total_pages": 150 }
    }

Inicio rápido

Reemplaza mk_live_tu_api_key con tu key real.

curl -X GET "https://app.mindy.gt/api/v1/products?per_page=5" \
  -H "Authorization: Bearer mk_live_tu_api_key"
const res = await fetch(
  "https://app.mindy.gt/api/v1/products?per_page=5",
  { headers: { Authorization: "Bearer mk_live_tu_api_key" } }
);
const data = await res.json();
import requests
r = requests.get(
    "https://app.mindy.gt/api/v1/products",
    params={"per_page": 5},
    headers={"Authorization": "Bearer mk_live_tu_api_key"}
)
print(r.json())
$ch = curl_init("https://app.mindy.gt/api/v1/products?per_page=5");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer mk_live_tu_api_key"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);

Respuesta · 200 OK

{
  "success": true,
  "data": [
    { "id": 42, "name": "Laptop HP 15", "sale_price": 4500.00,
      "category_id": 5, "is_active": true, "has_variants": false }
  ],
  "meta": { "page": 1, "per_page": 5, "total": 150, "total_pages": 30 }
}

Autenticación

Incluye tu API Key en el header Authorization de cada petición:

Authorization: Bearer mk_live_tu_api_key

Scopes disponibles

ScopeDescripción
catalog:readSiempre incluido. Productos, categorías, paquetes, sucursales, proveedores, marcas, unidades, clientes, stock y recordatorios
sales:readLeer historial de ventas y detalles
sales:writeCrear ventas y anularlas
products:writeCrear, editar y eliminar productos
clients:writeCrear y editar clientes
expenses:writeCrear egresos
reminders:writeCrear, editar y eliminar recordatorios
stock:writeAjustar stock de productos
reports:readEgresos, resumen del período y cierres de caja
employees:readLeer lista de empleados
subscriptions:readLeer suscripciones, cargos y planes
webhooks:manageCrear, editar, eliminar y listar webhooks
crm:readLeer leads, mensajes, notas, bitácora, embudos y tags
crm:writeCrear leads, actualizarlos y agregar notas
crm:messages:sendEnviar mensajes a leads por cualquier canal

Errores

Todas las respuestas de error tienen esta estructura:

{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Producto no encontrado",
    "status": 404
  }
}
HTTPCódigoDescripción
400VALIDATION_ERRORCampos requeridos faltantes o formato inválido
400INSUFFICIENT_STOCKStock insuficiente para la operación
401UNAUTHORIZEDAPI Key inválida, revocada o expirada
403FORBIDDENLa key no tiene el scope requerido
404NOT_FOUNDRecurso no encontrado
409CONFLICTDuplicado (barcode, SKU, teléfono) o estado ya aplicado
422UNSUPPORTED_MEDIA_TYPE_FOR_CHANNELEl canal no soporta ese tipo de media
429RATE_LIMIT_EXCEEDEDDemasiadas peticiones
502CHANNEL_API_ERRORCanal externo rechazó el envío

Rate Limiting

Límite configurable por API Key: 30, 60, 120 o 300 req/min. Cada respuesta incluye:

HeaderDescripción
X-RateLimit-LimitLímite total por minuto
X-RateLimit-RemainingRequests restantes en la ventana
X-RateLimit-ResetTimestamp Unix de reinicio

Al exceder el límite recibirás un 429. Espera al reinicio antes de reintentar.

Paginación

ParámetroDefaultDescripción
page1Número de página
per_page20Resultados por página (máx 100)
{
  "success": true,
  "data": [...],
  "meta": { "page": 1, "per_page": 20, "total": 150, "total_pages": 8 }
}
Cuenta

Mi cuenta

GET/v1/meInfo de la API key actual — cualquier key válida

Útil para introspección y debugging: muestra los scopes, el límite y el comercio asociado a la key.

{
  "success": true,
  "data": {
    "key_prefix": "mk_live_a1b2",
    "name": "Tienda online",
    "commerce_id": 42,
    "commerce_name": "Mi Negocio GT",
    "scopes": ["catalog:read", "sales:read", "products:write"],
    "rate_limit": 60,
    "is_active": true,
    "last_used_at": "2026-06-03 14:22:11",
    "expires_at": null,
    "created_at": "2026-05-10 09:00:00"
  }
}
Catálogo

Productos

GET/v1/productsLista con filtros y paginación
ParámetroTipoDescripción
searchstringBuscar por nombre o descripción
barcode / skustringCoincidencia exacta
category_idintFiltrar por categoría
active_only0/1Solo activos (default 1)
includestringvariants, stock (separados por coma)
curl "https://app.mindy.gt/api/v1/products?per_page=5&include=stock" \
  -H "Authorization: Bearer mk_live_tu_api_key"
const res = await fetch(
  "https://app.mindy.gt/api/v1/products?per_page=5&include=stock",
  { headers: { Authorization: "Bearer mk_live_tu_api_key" } }
);
const { data, meta } = await res.json();
import requests
r = requests.get(
    "https://app.mindy.gt/api/v1/products",
    params={"per_page": 5, "include": "stock"},
    headers={"Authorization": "Bearer mk_live_tu_api_key"}
)
print(r.json())
$ch = curl_init(
  "https://app.mindy.gt/api/v1/products?per_page=5&include=stock"
);
curl_setopt($ch, CURLOPT_HTTPHEADER,
  ["Authorization: Bearer mk_live_tu_api_key"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
GET/v1/products/{id}Producto individual con variantes y stock
GET/v1/products/{id}/variantsVariantes con stock por sucursal
GET/v1/products/{id}/stockStock por sucursal
GET/v1/products/{id}/movementsHistorial de movimientos de stock — paginado
ParámetroDescripción
per_pageMáx 200 (default 50)
branch_idFiltrar por sucursal
date_from / date_toRango de fechas (YYYY-MM-DD)
{
  "id": 5821,
  "type": "-",
  "quantity": 3.0,
  "description": "Por venta API. R#000142",
  "date": "2026-06-01 10:35:00",
  "branch_id": 1,
  "branch_name": "Sucursal Central"
}
type es "+" (entrada) o "-" (salida).
POST/v1/productsCrear — scope: products:write · 201
curl -X POST "https://app.mindy.gt/api/v1/products" \
  -H "Authorization: Bearer mk_live_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Camiseta Polo",
    "sale_price": 150.00,
    "barcode": "7501234567890",
    "category_id": 3
  }'
const res = await fetch(
  "https://app.mindy.gt/api/v1/products",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer mk_live_tu_api_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Camiseta Polo",
      sale_price: 150.00,
      barcode: "7501234567890",
      category_id: 3,
    }),
  }
);
const product = await res.json();
import requests
r = requests.post(
    "https://app.mindy.gt/api/v1/products",
    headers={"Authorization": "Bearer mk_live_tu_api_key"},
    json={
        "name": "Camiseta Polo",
        "sale_price": 150.00,
        "barcode": "7501234567890",
        "category_id": 3,
    }
)
print(r.json())  # 201 Created
$ch = curl_init("https://app.mindy.gt/api/v1/products");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer mk_live_tu_api_key",
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "name" => "Camiseta Polo",
    "sale_price" => 150.00,
    "barcode" => "7501234567890",
    "category_id" => 3,
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);

201 Created

// 201 Created
{
  "success": true,
  "data": {
    "id": 55, "name": "Camiseta Polo", "sale_price": 150.00,
    "barcode": "7501234567890", "category_id": 3,
    "is_active": true, "has_variants": false,
    "stock": [{ "branch_id": 1, "branch_name": "Central", "quantity": 0 }]
  }
}
POST/v1/products/batchCrear/actualizar hasta 50 — scope: products:write

Cada item se procesa de forma independiente; un error en uno no cancela los demás. Modos: create (falla por item si el barcode existe) o upsert (actualiza si existe, crea si no).

{
  "mode": "upsert",
  "products": [
    {
      "name": "Agua 500ml",
      "sale_price": 3.50,
      "purchase_price": 1.80,
      "barcode": "7501234567890",
      "sku": "AGU-500",
      "category_id": 5,
      "min_stock": 10
    }
  ]
}

Respuesta

{
  "success": true,
  "data": {
    "mode": "upsert",
    "processed": 3, "succeeded": 3, "failed": 0,
    "results": [
      { "index": 0, "success": true,  "id": 201, "name": "Agua 500ml",   "action": "created" },
      { "index": 1, "success": true,  "id": 88,  "name": "Coca-Cola 1L", "action": "updated" },
      { "index": 2, "success": false, "error": "sale_price es requerido y debe ser > 0" }
    ]
  }
}
PUT/v1/products/{id}Editar — scope: products:write · Partial update
DELETE/v1/products/{id}Eliminar (soft delete) — dispara product.deleted
{ "success": true, "data": { "id": 88, "deleted": true } }
Al crear un producto se genera stock en 0 para cada sucursal. Barcode y SKU son únicos por comercio.

Stock

GET/v1/stockConsulta masiva — hasta 100 productos
ParámetroDescripción
product_ids *IDs separados por coma (máx 100)
branch_idFiltrar por sucursal
{
  "success": true,
  "data": [{
    "product_id": 1,
    "stock": [{ "branch_id": 1, "branch_name": "Central", "quantity": 25 }],
    "total_stock": 25, "low_stock": false, "minimum_stock": 5
  }]
}
POST/v1/stock/adjustAjustar stock — scope: stock:write

Cantidad positiva = entrada, negativa = salida. Valida que no quede stock negativo. Dispara stock.adjusted.

{
  "product_id": 88,
  "branch_id": 1,
  "quantity": -5,
  "reason": "Merma por daño en almacén"
}

Respuesta

{
  "success": true,
  "data": {
    "product_id": 88, "product_name": "Coca-Cola 1L",
    "branch_id": 1, "adjustment": -5,
    "new_stock": 45.0, "adjusted_at": "2026-06-03 11:00:00"
  }
}

Categorías

GET/v1/categoriesLista de categorías
GET/v1/categories/{id}Categoría individual
GET/v1/categories/{id}/productsProductos de una categoría

Paquetes

GET/v1/packagesLista de paquetes/combos
GET/v1/packages/{id}Paquete con componentes
{
  "id": 10, "name": "Combo Almuerzo", "sale_price": 45.00,
  "components": [
    { "product_id": 20, "product_name": "Hamburguesa", "quantity": 1 },
    { "product_id": 31, "product_name": "Papas fritas", "quantity": 1 }
  ]
}

Sucursales

GET/v1/branchesLista de sucursales
{
  "id": 1, "name": "Sucursal Central",
  "address": "6ta Avenida 10-20, Zona 1",
  "phone": "22334455", "email": "central@negocio.gt", "is_default": true
}

Proveedores

GET/v1/providersLista — filtros: search, active_only
GET/v1/providers/{id}Proveedor individual
{
  "id": 7, "name": "Distribuidora XYZ",
  "address": "7a Av. 15-30, Guatemala",
  "email": "ventas@xyz.gt", "phone": "24441234",
  "phone_2": null, "nit": "12345678", "is_active": true
}

Marcas y Unidades

GET/v1/brands · /v1/brands/{id}Marcas de productos
{ "id": 3, "name": "Coca-Cola", "is_active": true }
GET/v1/units · /v1/units/{id}Unidades de medida
{ "id": 2, "name": "Litro", "is_active": true }
GET/v1/expense-categoriesCategorías de egresos
[
  { "id": 12, "name": "Servicios básicos", "is_active": true },
  { "id": 13, "name": "Transporte", "is_active": true }
]

Clientes

GET/v1/clientsLista — filtros: search, phone
GET/v1/clients/{id}Cliente individual
GET/v1/clients/{id}/creditSaldo de crédito y desglose
{
  "success": true,
  "data": {
    "client_id": 55,
    "total_credit": 850.00, "total_paid": 500.00, "total_balance": 350.00,
    "credits": [
      { "id": 14, "sale_id": 3021, "amount": 500.00, "paid": 500.00,
        "balance": 0.00, "expires_at": "2026-07-01", "is_paid": true },
      { "id": 18, "sale_id": 3105, "amount": 350.00, "paid": 0.00,
        "balance": 350.00, "expires_at": "2026-08-15", "is_paid": false }
    ]
  }
}
POST/v1/clientsCrear — scope: clients:write · 201
CampoReq.Descripción
name*Nombre (máx 250)
phone*Teléfono (8 dígitos exactos)
nitNIT fiscal
email / address / dpiOpcionales
{ "name": "María López", "phone": "55001234", "nit": "123456-7" }

201 Created

// 201 Created
{
  "success": true,
  "data": {
    "id": 100, "name": "María López", "nit": "123456-7",
    "phone": "55001234", "is_active": true,
    "has_credit": false, "credit_limit": 0
  }
}
PUT/v1/clients/{id}Editar — scope: clients:write · Partial update

Recordatorios

GET/v1/remindersLista — filtros: fecha, sucursal, cliente, estado
GET/v1/reminders/{id}Recordatorio individual
POST/v1/remindersCrear — scope: reminders:write · 201
{
  "start_date": "2026-06-10 10:00:00",
  "end_date": "2026-06-10 11:00:00",
  "description": "Cita de seguimiento",
  "branch_id": 1,
  "client_id": 55,
  "category_id": 3
}
PUT/v1/reminders/{id}Editar — campos: start_date, end_date, description, category_id, client_id, is_cancelled
DELETE/v1/reminders/{id}Eliminar (soft delete)
Ventas y Finanzas

Ventas

GET/v1/salesHistorial — scope: sales:read
ParámetroDescripción
date_from / date_toRango de fechas (YYYY-MM-DD)
branch_id / client_idFiltrar por sucursal o cliente
payment_methodBúsqueda parcial en método de pago
status1 = activa, 0 = anulada
{
  "id": 3105, "sale_number": "3105",
  "date": "2026-06-03 10:12:00",
  "payment_method": "Efectivo", "document_type": "VEN",
  "invoice_number": null,
  "total": 125.50, "total_discount": 0.00,
  "total_profit": 45.20, "total_cost": 80.30,
  "status": 1,
  "client_id": 55, "client_name": "Juan Pérez",
  "branch_id": 1, "branch_name": "Sucursal Central",
  "credit_amount": 0.00, "paid_amount": 0.00
}
GET/v1/sales/{id}Detalle con artículos
{
  "...campos de la venta...",
  "items": [
    {
      "id": 9021, "product_id": 88, "product_name": "Coca-Cola 1L",
      "quantity": 2.0, "unit_price": 12.00, "cost_price": 7.50,
      "discount": 0.00, "line_total": 24.00
    }
  ]
}
POST/v1/salesCrear — scope: sales:write · 201

Descuenta stock automáticamente. payment_method: efectivo, tarjeta o transferencia.

curl -X POST "https://app.mindy.gt/api/v1/sales" \
  -H "Authorization: Bearer mk_live_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "branch_id": 1,
    "payment_method": "efectivo",
    "client_id": 55,
    "items": [
      { "product_id": 88, "quantity": 2, "unit_price": 12.00 }
    ]
  }'
const res = await fetch(
  "https://app.mindy.gt/api/v1/sales",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer mk_live_tu_api_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      branch_id: 1,
      payment_method: "efectivo",
      client_id: 55,
      items: [{ product_id: 88, quantity: 2, unit_price: 12.00 }],
    }),
  }
);
const sale = await res.json();  // 201 Created
import requests
r = requests.post(
    "https://app.mindy.gt/api/v1/sales",
    headers={"Authorization": "Bearer mk_live_tu_api_key"},
    json={
        "branch_id": 1,
        "payment_method": "efectivo",
        "client_id": 55,
        "items": [{"product_id": 88, "quantity": 2, "unit_price": 12.00}],
    }
)
print(r.status_code, r.json())
$ch = curl_init("https://app.mindy.gt/api/v1/sales");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer mk_live_tu_api_key",
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "branch_id" => 1,
    "payment_method" => "efectivo",
    "items" => [
      ["product_id" => 88, "quantity" => 2, "unit_price" => 12.00],
    ],
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);
POST/v1/sales/{id}/cancelAnular y restaurar stock — scope: sales:write

Sin body. Dispara sale.canceled. Devuelve 409 si ya está anulada.

{
  "success": true,
  "data": {
    "id": 3105, "sale_number": "3105",
    "status": "canceled", "canceled_at": "2026-06-03 15:00:00"
  }
}

Egresos

GET/v1/expenses · /v1/expenses/{id}scope: reports:read

Filtros: date_from, date_to, branch_id, type.

{
  "id": 210, "description": "Pago de energía eléctrica",
  "date": "2026-06-01 09:00:00", "type": "Gasto",
  "amount": 350.00, "reference": "Factura #4521", "notes": "",
  "category_id": 12, "branch_id": 1, "branch_name": "Sucursal Central"
}
POST/v1/expensesCrear — scope: expenses:write · 201

date, type, reference, notes y category_id son opcionales.

{
  "description": "Pago de energía eléctrica",
  "amount": 350.00,
  "branch_id": 1,
  "type": "Gasto",
  "reference": "Factura #4521",
  "category_id": 12
}

Reportes

GET/v1/reports/summaryResumen financiero — scope: reports:read

Ventas, costos, utilidad, egresos, resultado neto y desglose por día y método de pago. Filtros: date_from, date_to, branch_id (default: hoy).

{
  "success": true,
  "data": {
    "period": { "from": "2026-06-01", "to": "2026-06-03" },
    "sales": {
      "count": 87, "revenue": 12450.00, "cost": 7200.00,
      "profit": 5250.00, "discounts": 125.00
    },
    "expenses": { "count": 5, "total": 1800.00 },
    "net_result": 10650.00,
    "by_payment_method": [
      { "payment_method": "Efectivo", "count": 60, "total": 8200.00 },
      { "payment_method": "Tarjeta de crédito / débito", "count": 27, "total": 4250.00 }
    ],
    "by_day": [
      { "date": "2026-06-01", "count": 30, "total": 4100.00 },
      { "date": "2026-06-02", "count": 28, "total": 3900.00 }
    ]
  }
}

Cierres de Caja

GET/v1/cash-closingsHistorial — scope: reports:read

Filtros: branch_id, date_from, date_to.

{
  "id": 88,
  "opened_at": "2026-06-03 08:00:00",
  "closed_at": "2026-06-03 20:00:00",
  "opening_amount": 500.00, "cash_withdrawal": 200.00,
  "cash_counted": 4350.00, "cash_calculated": 4350.00,
  "cash_difference": 0.00, "is_closed": true,
  "notes": "Cierre sin novedad",
  "opened_by_id": 4, "opened_by_name": "María García",
  "closed_by_id": 4, "closed_by_name": "María García",
  "branch_id": 1, "branch_name": "Sucursal Central",
  "cash_register_id": 2
}
GET/v1/cash-closings/{id}Detalle con sales_summary del período
{
  "...campos del cierre...",
  "sales_summary": { "count": 47, "total": 5800.00 }
}
Plataforma

Webhooks

Recibe eventos en tiempo real en tu propia URL. Requiere scope webhooks:manage. Máximo 10 webhooks por comercio.

GET/v1/webhooks · /v1/webhooks/{id}Lista y detalle
GET/v1/webhooks/eventsEvent types disponibles
{
  "id": 3,
  "url": "https://mi-sistema.com/webhook",
  "events": ["sale.created", "client.created"],
  "description": "Sync con ERP",
  "is_active": true, "fail_streak": 0, "disabled_at": null,
  "created_at": "2026-05-01 09:00:00", "updated_at": null
}
El secret no se retorna en GET — solo se muestra una vez al crear el webhook (POST).
POST/v1/webhooksCrear · 201 — el secret se muestra una sola vez
{
  "url": "https://mi-sistema.com/webhook",
  "events": ["sale.created", "product.updated"],
  "description": "Sync con ERP",
  "is_active": true
}

201 Created

// 201 Created — el secret se muestra UNA sola vez
{
  "success": true,
  "data": {
    "id": 3,
    "url": "https://mi-sistema.com/webhook",
    "events": ["sale.created", "product.updated"],
    "secret": "a1b2c3d4e5f6...",
    "is_active": true
  }
}
PUT/v1/webhooks/{id}Editar url, events, description o is_active
DELETE/v1/webhooks/{id}Eliminar webhook e historial

Eventos disponibles

EventoDescripción
sale.createdSe creó una venta (POS o API)
sale.refundedSe procesó una devolución
sale.canceledSe anuló una venta
product.createdSe creó un producto
product.updatedSe actualizó un producto
product.deletedSe eliminó un producto
client.createdSe creó un cliente
client.updatedSe actualizó un cliente
stock.lowStock bajo detectado (cron diario)
stock.adjustedAjuste manual de stock
crm.lead.createdLead nuevo en el CRM
crm.lead.stage_changedCambio de etapa del pipeline
crm.lead.assignedCambio de colaborador asignado
crm.lead.archivedLead archivado/ganado/perdido
crm.message.receivedMensaje entrante recibido
crm.note.addedNota interna agregada

Verificación de firma

Cada POST a tu URL incluye este header. Calcula HMAC-SHA256(secret, body_raw) y compara con tiempo constante para evitar timing attacks.

X-Mindy-Signature: sha256=<hmac-sha256(secret, body)>

Empleados

GET/v1/employees · /v1/employees/{id}scope: employees:read

Lista de empleados con su rol. No incluye contraseñas ni datos biométricos. Filtros: search, active_only.

{
  "id": 4, "name": "María García",
  "email": "maria@minegocio.gt", "phone": "50001234",
  "photo_url": "https://cdn.mindy.gt/...",
  "role_id": 2, "role_name": "Cajero",
  "is_active": true,
  "created_at": "2025-01-10 08:00:00",
  "last_login_at": "2026-06-03 07:45:00"
}

Suscripciones

GET/v1/subscriptions · /v1/subscriptions/{id}scope: subscriptions:read
GET/v1/plansPlanes de suscripción configurados

Consulta las suscripciones recurrentes de tus clientes, sus cargos generados y los planes disponibles. Solo lectura.

CRM

CRM — Leads

Cubre WhatsApp, Messenger, Instagram, Telegram, Email y Mindy Chat. Lectura con crm:read, escritura con crm:write.

GET/v1/crm/leadsLista con filtros
ParámetroDescripción
funnel_idFiltrar por embudo
pipeline_stateMatch exacto contra state_key
assigned_user_id0 = sin asignar
archived_statusnone | archived | won | lost
tag_idFiltrar por tag
search / phoneLIKE en nombre y teléfono
updated_sinceupdated_at >= ?
GET/v1/crm/leads/{id}Lead con tags, key_dates y notas
GET/v1/crm/leads/{id}/messagesMensajes (incluye multimedia)
GET/v1/crm/leads/{id}/notesNotas internas del equipo
GET/v1/crm/leads/{id}/historyBitácora de cambios
{
  "id": 123, "phone": "50212345678", "contact_name": "Juan",
  "channel_label": "WhatsApp Ventas",
  "funnel_id": 1, "pipeline_state": "qualified",
  "archived_status": "none", "assigned_user_id": 45,
  "last_message_at": "2026-05-19 14:32:00",
  "tags": [{ "id": 2, "label": "VIP", "color": "#7c3aed" }],
  "created_at": "2026-05-01 09:00:00"
}
POST/v1/crm/leadsCrear — scope: crm:write · 201

Solo phone es requerido. Devuelve 409 si ya existe un lead con ese teléfono.

{
  "phone": "50212345678",
  "contact_name": "Carlos Mendoza",
  "funnel_id": 1,
  "pipeline_state": "new",
  "notes": "Interesado en plan pro"
}
PUT/v1/crm/leads/{id}Actualizar — scope: crm:write

Dispara webhooks automáticamente: cambiar pipeline_statecrm.lead.stage_changed, archived_statuscrm.lead.archived, assigned_user_idcrm.lead.assigned. Usa assigned_user_id: null para desasignar.

{
  "contact_name": "Carlos Mendoza Ruiz",
  "pipeline_state": "qualified",
  "archived_status": "won",
  "assigned_user_id": 4,
  "notes": "Cerrado exitosamente"
}

CRM — Enviar mensaje

POST/v1/crm/leads/{id}/messagesscope: crm:messages:send

Enruta automáticamente por el canal del lead. type: text, image, video, audio o document.

curl -X POST "https://app.mindy.gt/api/v1/crm/leads/123/messages" \
  -H "Authorization: Bearer mk_live_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "type": "text", "content": "Hola, te confirmo tu pedido." }'
const res = await fetch(
  "https://app.mindy.gt/api/v1/crm/leads/123/messages",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer mk_live_tu_api_key",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ type: "text", content: "Hola, te confirmo tu pedido." }),
  }
);
const msg = await res.json();
import requests
r = requests.post(
    "https://app.mindy.gt/api/v1/crm/leads/123/messages",
    headers={"Authorization": "Bearer mk_live_tu_api_key"},
    json={"type": "text", "content": "Hola, te confirmo tu pedido."}
)
print(r.json())
$ch = curl_init(
  "https://app.mindy.gt/api/v1/crm/leads/123/messages"
);
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer mk_live_tu_api_key",
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "type" => "text",
    "content" => "Hola, te confirmo tu pedido.",
  ]),
  CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);

200 OK

// 200 OK
{
  "success": true,
  "data": {
    "message_id": 9999, "channel": "whatsapp",
    "external_id": "EVO_abc123", "sent_at": "2026-05-19 14:32:00"
  }
}

Soporte por canal

Canaltextimagevideoaudiodocument
WhatsApp
Mindy Chat
Instagram
Messenger
Telegram
Email

CRM — Notas

POST/v1/crm/leads/{id}/notesAgregar nota — scope: crm:write · 201

Dispara crm.note.added.

{ "content": "Llamó para confirmar reunión del martes." }

201 Created

// 201 Created
{
  "id": 88, "id_lead": 14,
  "content": "Llamó para confirmar reunión del martes.",
  "user_name": null, "created_at": "2026-06-03 11:30:00"
}

CRM — Embudos, Etapas y Tags

GET/v1/crm/funnelsEmbudos con etapas anidadas
GET/v1/crm/pipelinesLista plana de todas las etapas
GET/v1/crm/tagsTags del CRM (id, label, color)
Otros

Changelog

v1.4.02026-06-03
  • Nuevo GET /v1/me, movimientos de stock, batch de productos, DELETE de productos
  • Nuevo Ventas: GET /v1/sales, POST /v1/sales/{id}/cancel
  • Nuevo Egresos, reportes de período, cierres de caja, ajuste de stock
  • Nuevo Webhooks gestionables vía API + 16 eventos · Empleados · Proveedores · Marcas · Unidades
  • Nuevo CRM: crear/editar leads, agregar notas
v1.3.02026-05-19
  • Nuevo Endpoints CRM: leads, mensajes, notas, historial, embudos, tags
  • Nuevo Enviar mensajes multicanal · Scopes crm:read y crm:messages:send
v1.2.02026-04-12
  • Nuevo Crear/editar productos, consulta masiva de stock, crear ventas
v1.0.02026-04-10
  • Nuevo Lanzamiento: catálogo, clientes, recordatorios, autenticación y paginación

Gana 20% cada mes con Mindy

Refiérelo y gana comisión recurrente. Sin costo, sin tope.

Quiero ganar