Changelog

Novedades de la API

Cada cambio de la superficie pública queda registrado aquí, del más nuevo al más viejo. Son 141 entradas en 12 páginas; esta trae las más recientes. También puedes suscribirte al feed.

v3.18.0 Seguridad

Las variables secretas ya no se devuelven en claro

Una variable marcada como secreta (is_encrypted: true, encrypted: true en el SDK) ahora es de solo escritura hacia afuera. Hasta hoy la marca no ocultaba nada: GET /variables devolvía el valor tal cual, y cualquier llave con variables:read leía los tokens de tus Funciones HTTP y el secreto con el que Meteor firma sus llamadas.

  • GET /variables, GET /variables/{id}, POST /variables y PUT /variables/{id} devuelven las secretas con value: null y un campo nuevo, has_value, que dice si hay un valor guardado.
  • POST /variables/resolve deja el token de una variable secreta sin reemplazar.
  • La herramienta met_list_variables del servidor MCP tampoco lo muestra.

Los flujos, los Mets y las Funciones IA la siguen leyendo por dentro: nada de lo que ya funciona deja de funcionar. Para cambiar el valor, envíalo con PUT; no hace falta leerlo antes.

Si guardaste un token en una variable sin marcarla como secreta, márcala: las que no lo están se devuelven igual que antes.

v3.17.1 Corrección

Quedarte sin Energía ya no bloquea la API (402 energy_depleted)

Un workspace sin Energía recibía 403 workspace_blocked («El workspace no tiene una suscripción activa») en todas las llamadas con API key, lecturas incluidas, aunque GET /billing/access respondiera blocked: false y la app siguiera funcionando. Quedarte sin Energía no es perder la suscripción, y ahora la API lo trata así:

  • Las lecturas y las operaciones que no consumen Energía responden normal.
  • Las que sí la consumen responden 402 con code: "energy_depleted" y no se ejecutan: POST /workspaces/{workspaceId}/runs, POST /workspaces/{workspaceId}/images/generate, /images/edit, /images/video, POST /contacts/{id}/extract y POST /contacts/{id}/enrich. La especificación documenta ese 402 en cada una. Se resuelve recargando Energía; reintentar antes no cambia nada.
  • 403 workspace_blocked queda para la cuenta bloqueada (plan inactivo o vencido, prueba terminada, tope de contactos…), con el mismo criterio que GET /billing/access (blocked: true). Su message ahora dice el motivo concreto.

En los SDK, el 402 llega como MetInvalidRequestError con code === 'energy_depleted' y no se reintenta solo. Por el servidor MCP, la tool recibe el mismo código con la pista de avisarle a la persona en vez de reintentar.

Guía: Errores, idempotencia y paginación.

v3.17.0 Nuevo

Cada contacto trae el enlace a su conversación (app_url)

Los contactos traen un campo nuevo, app_url: el enlace que abre la conversación del contacto en Meteor. Sirve para guardarlo en la ficha del cliente de tu CRM y llegar al chat con un clic.

  • GET /contacts, GET /contacts/{contactId} y POST /contacts lo devuelven.
  • Los eventos que hablan de un contacto lo llevan en data: contact.created, contact.updated, contact.message.received, contact.message.sent, contact.message.failed, conversation.handoff y call.missed. contact.deleted no lo lleva.
  • En los flujos está disponible como $contact.app_url.

El enlace es estable: lleva el id del workspace y el del contacto, no el nombre del workspace, así que sigue funcionando si el workspace cambia de nombre. Abrirlo pide una sesión en Meteor con acceso a ese contacto.

Receta: Enlace a la conversación desde tu CRM.

v3.16.2 Corrección

Los patrones de validación de la especificación se publican sin barras

Algunos campos de la especificación declaraban su pattern con las barras de una expresión literal de JavaScript (por ejemplo "/^[A-Za-z]{2}$/"). Un validador generado desde la especificación exigía esas barras y rechazaba valores correctos como CO. Ahora cada pattern es solo la expresión ("^[A-Za-z]{2}$").

Campos corregidos en la especificación pública:

  • country y owner_phone al aprovisionar un workspace.
  • recurrence_time al crear o editar una difusión.
  • slug al actualizar el slug.

En la especificación de Partners se corrigieron los campos equivalentes del aprovisionamiento de cuentas (country, owner_phone) y el token de la confirmación de correo, que ahora acepta hexadecimal en mayúsculas o minúsculas, igual que el servicio.

El comportamiento del servicio no cambia: valida exactamente lo mismo que antes.

v3.16.1 Corrección

La especificación incluye el filtro de conversaciones sin respuesta

La especificación pública de Contactos documenta ahora el parámetro opcional unanswered tanto en GET /contacts como en GET /contacts/counts. El comportamiento del servicio no cambia: el filtro ya estaba disponible.

También se sincronizó la versión publicada de la especificación de Partners con el changelog vigente. No cambian rutas, permisos ni respuestas de Partners.

v3.16.0 Cambio

Reconocimiento de clientes LyP sin confirmaciones implícitas

La herramienta reconocer_cliente_actual ahora declara de forma explícita que reconocer al contacto no registra un pedido ni crea una cotización. Su respuesta incluye pedido_registrado: false y cotizacion_creada: false para que el asistente no confirme operaciones que todavía no existen.

Cuando el mensaje contiene un pedido explícito, el asistente debe continuar con cotizar_pedido_lyp y solo confirmar el registro si esa herramienta devuelve cotizacion_creada: true.

La selección de precios también prioriza la identificación autoritativa del Rutero antes del teléfono o el nombre. Así evita mezclar productos históricos de otro cliente sin desactivar el bloqueo de seguridad ante una contradicción real.

v2.30.3 Nuevo

Filtro de conversaciones pendientes de respuesta

La bandeja de Contactos ahora incluye el filtro rápido Sin respuesta. Una conversación aparece allí cuando el cliente escribió y todavía no recibió una respuesta posterior de una persona del equipo. Las respuestas del asistente y los mensajes automáticos no retiran el chat del filtro.

Abrir o leer el chat no cambia esta clasificación. Las notas internas, los eventos del sistema y los mensajes salientes cuya entrega falló tampoco se consideran respuestas.

La API pública de contactos acepta el parámetro opcional unanswered=true en el listado y en los conteos de bandeja.

v3.15.0 Cambio

Cada workspace tiene un límite de tareas por minuto, y lo que pasa del límite espera su turno

Desde esta versión, cada workspace tiene un límite de tareas por minuto según su plan. Una tarea es cada vez que corre un flujo: una automatización, una difusión, un mensaje que entra por un canal, un salto a otro flujo o una prueba manual. Las Funciones IA no cuentan: el Met que las llama espera la respuesta y no puede hacer fila.

Nada se pierde al pasarse. Lo que supera el límite espera su turno del minuto siguiente y arranca solo. Si una tarea espera más de 10 minutos, o la fila del workspace ya tiene 10 minutos de capacidad, se rechaza con un mensaje que dice qué hacer. Al primer freno de cada hora, los admins del workspace reciben un aviso.

PlanTareas por minuto
Developer y Tech Partner4
Escritorio8
Estudio20
Oficina80
Edificio240
Ciudadela800
Campus3.000

Si tu operación necesita más, el complemento «20 tareas extra por minuto» (US$15 al mes, acumulable) sube el límite.

Cómo consultarlo

GET /billing/limits trae el recurso nuevo automation_tasks_per_minute:

  • limit es el límite que se aplica hoy (plan más complementos).
  • used es el minuto con más tareas de las últimas 24 horas, para que veas qué tan cerca llegas en tus picos.
  • period es minute y resets_at va en null.
  • enforcement es queue, un valor nuevo: lo que pasa del límite hace fila en vez de rechazarse o de solo avisar.

Si tu integración valida period o enforcement contra una lista cerrada, agrega minute y queue.

v3.14.0 Cambio

Reemplaza colecciones con claves compuestas y una vista previa vinculante

POST /collections/{collectionId}/items/import admite ahora keyFields en el modo replace_active. Puedes combinar hasta cuatro campos para identificar una fila, por ejemplo identificacion_del_cliente + codigo cuando un mismo cliente tiene varios productos. keyField sigue disponible y conserva compatibilidad con las integraciones existentes.

La vista previa devuelve preview_token. Debes reenviarlo como previewToken al confirmar. Si otro usuario modifica los registros vigentes después de la vista previa, la confirmación responde con conflicto y no escribe cambios. Esto evita aplicar un cálculo desactualizado sobre una colección que ya cambió.

Cuando el reemplazo archivaría 30 % o más de los registros vigentes, también se requiere allowHighArchive: true. La operación continúa siendo transaccional: los ausentes se archivan sin borrado físico, los códigos de registros existentes se conservan y cualquier error revierte el reemplazo completo.

v3.13.0 Nuevo

Cambia los eventos de un webhook sin cambiar su secreto

Hasta hoy, para sumar un evento a un endpoint había que borrarlo y crear otro, y con eso cambiaba el secreto de firma: tocaba redesplegar la verificación.

Ahora PATCH /webhook-endpoints/{id} cambia los eventos, la URL o la descripción de un endpoint, y el secreto sigue siendo el mismo. enabled_events reemplaza la lista completa: para sumar un evento, manda la actual más el nuevo.

const [ep] = await met.webhooks.subscriptions.list();
await met.webhooks.subscriptions.update(ep.id, {
  enabled_events: [...ep.enabled_events, 'contact.message.failed'],
});

El método update está en el SDK desde @meteor.ia/sdk 0.9.1, y en Python desde meteor-ia 0.3.0: met.webhooks.subscriptions.update(endpoint_id, enabled_events=[...]).

Manda al menos un campo. Cambiar la URL no reactiva un endpoint que se apagó por fallos seguidos.

v3.12.0 Nuevo

Nuevo evento contact.message.failed, con el código de error de WhatsApp

Hasta hoy, un mensaje que no llegaba solo se veía consultando GET /contacts/{id}/messages contacto por contacto. Ahora sale el evento contact.message.failed (scope conversations:read) cuando un mensaje hacia un contacto queda sin entregar: porque el proveedor lo rechazó al enviarlo, o porque WhatsApp lo marcó como fallido después.

Trae los mismos campos que contact.message.sent, más:

CampoQué es
error.codeEl código de WhatsApp, por ejemplo 131049. null si el proveedor no mandó uno.
error.messageEl motivo en texto.
template_nameEl nombre de la plantilla, si era una. null en otro caso.
{
  "type": "contact.message.failed",
  "data": {
    "contact_id": 4821,
    "message_id": 458112,
    "channel_type": "whatsapp",
    "message_type": "template",
    "origin": "api",
    "template_name": "recordatorio_pago",
    "error": { "code": 131026, "message": "Message undeliverable" }
  }
}

El código separa causas que piden cosas distintas:

  • 131026: el número no puede recibir el mensaje. Revísalo antes de volver a enviar.
  • 131049: Meta frenó las plantillas de marketing a esa persona. El número está bien; espacia los envíos.
  • 132012, 131009, 132000: un parámetro no coincide con la plantilla. Corrige el envío.

Sale una sola vez por mensaje. El mismo dato queda en metadata.delivery_status y metadata.delivery_error de cada mensaje en GET /contacts/{id}/messages.

v3.11.0 Nuevo

Cada mensaje dice quién lo originó, también cuando sale con tu API key

Hasta hoy, un mensaje que tu integración mandaba con su API key quedaba igual que uno escrito a mano desde la bandeja: role: operator y el sent_by_user_id del dueño de la key. No había forma de separarlos.

Ahora cada mensaje trae origin, en GET /contacts/{id}/messages, en la respuesta de los envíos y en el evento contact.message.sent:

originQuién lo originó
contactEl contacto.
metUn Met.
panelUna persona de tu equipo desde la app de Meteor (bandeja, ficha, notas).
apiUna integración con API key o con el SDK.
mcpUn asistente de IA conectado por MCP.
flowUn flujo.
automationUna automatización o un recordatorio programado.
broadcastUna difusión.
phoneEl dueño desde WhatsApp Business en su celular (coexistencia).
systemUn evento de la conversación: asignación, cambio de estado, etiqueta.
unknownUn mensaje anterior a este cambio cuyo origen no quedó registrado.

Los mensajes nuevos quedan marcados al enviarse. Los anteriores se deducen de lo que ya estaba guardado, con una excepción: lo que tu integración envió por API antes del 25 de septiembre de 2026 aparece como panel, porque en ese momento el origen no se registraba.

sent_from sigue en contact.message.sent sin cambios, para no romper a quien ya lo usa. origin es la versión fina.