Changelog · página 9 de 14

Novedades de la API

Cada cambio de la superficie pública queda registrado aquí, del más nuevo al más viejo. Esta página cubre del 30 jul 2026 – 5 ago 2026. También puedes suscribirte al feed.

Corrección

El CLI separa webhooks entrantes de entregas salientes

El comando met webhooks mezclaba dos direcciones distintas: lo que un tercero envía a Meteor y lo que Meteor entrega a tu servidor. Además, sample afirmaba enviar una prueba cuando solo leía el último payload entrante.

Ahora usa el grupo que corresponde al flujo:

# Tercero → Meteor: ver lo recibido, sin generar tráfico.
met inbound-webhooks events 12
met inbound-webhooks sample 12

# Meteor → tu servidor: probar, inspeccionar y reintentar entregas firmadas.
met webhook-endpoints test we_123
met webhook-endpoints deliveries we_123
met webhook-endpoints retry we_123 del_456

test y retry salen con código 0 solamente cuando tu servidor confirma la entrega. Si la API pudo iniciar el intento pero el receptor falla o vence el timeout, el CLI sale con 1, para que un pipeline no lo reporte como éxito.

met webhooks sigue disponible temporalmente como alias de met inbound-webhooks y avisa que está deprecado.

v2.25.0

Resuelto es el estado final de los tickets de soporte

La API de partners simplifica el ciclo de soporte: resolved es ahora el único estado terminal y closed deja de ser un valor aceptado.

Los estados válidos de PATCH /partner/tickets/:id/status son:

new · in_progress · waiting_client · resolved

Un ticket resuelto ya no se puede reabrir ni recibir respuestas. Si el problema persiste, crea un ticket nuevo; así cada caso conserva un inicio, una resolución y un historial inequívocos.

v2.25.0

Los contactos gemelos ahora se detectan y se pueden fusionar

Qué cambió

GET /contacts/{id}/duplicates gana un motivo de coincidencia nuevo: identidad.

Hasta hoy la búsqueda de duplicados comparaba teléfono y email. Eso deja fuera el caso más incómodo: dos contactos que son literalmente la misma conversación —mismo user_ns, mismo canal, misma línea—. Pasa cuando alguien te escribe por primera vez mandando varios mensajes seguidos, y también cuando una importación trae el número con + y el chat entrante lo trae sin él.

{
  "data": [
    {
      "id": 33325,
      "name": "Cristhian Martinez",
      "phone": "+573172723452",
      "matched_by": ["identidad", "phone"],
      "can_merge": true,
      "blocked_reason": null
    }
  ]
}

matched_by ahora puede traer "identidad" además de "phone" y "email". Es aditivo: si ya lees ese arreglo, los valores viejos siguen llegando igual.

Lo que antes no se podía fusionar, ahora sí

can_merge era false en cuanto los dos contactos tenían conversación propia, sin mirar si era la *misma* conversación. Los gemelos exactos caían justo ahí y quedaban trabados.

Ahora la regla mira el valor, no su presencia:

  • Misma identidad (mismo user_ns, mismo canal, misma línea) → can_merge: true. Se fusionan y la identidad que sobrevive queda en dígitos, sin +.
  • Identidades distintas con conversación propia cada una → sigue bloqueado, con su blocked_reason. Fusionarlas perdería una y el próximo mensaje desharía la fusión.

Un + de más no hace dos identidades: +573172723452 y 573172723452 son la misma persona. Los identificadores que no son teléfonos (los de Meteor CRM, los BSUID de WhatsApp) se siguen comparando literales.

Qué tienes que hacer

Nada obligatorio. Si guardas la identidad del contacto por tu lado, conviene que la normalices a dígitos: es el formato con el que entra WhatsApp y con el que queda un contacto después de fusionarse.

v2.11.0

Reporta conversiones web (Pixel) con deduplicación

Ahora puedes reportar conversiones web a Meta como espejo server-side de tu Pixel, para no perder eventos que el navegador bloquea (ad-blockers, iOS, cookies).

  • POST /api/v1/conversions/web — scope conversions:write.
  • Pasa el mismo event_id que disparaste en el navegador con fbq('track', …, { eventID }). Meta deduplica Pixel↔CAPI durante 48h con esa clave, así que el evento cuenta una sola vez aunque llegue por los dos caminos.
  • No es por-contacto: la coincidencia va por fbp/fbc (cookies del navegador) o por PII opcional (email, phone, external_id, que hasheamos antes de emitir). Necesitas al menos una clave de match.
  • Reenvía la IP y el User-Agent del usuario final (no los de tu servidor) en client_ip_address / client_user_agent para mejorar la coincidencia.
  • Opcionales: event_source_url, value, currency.
  • Devuelve la decisión real: accepted (encolado), discarded (con razón: integración inactiva, fuera de taxonomía, sin clave de match) o duplicate.

Los eventos web usan los nombres de evento del Pixel (no los de *business messaging* de WhatsApp) — configúralos en la taxonomía del workspace.

SDK: met.conversions.reportWeb({ event_name, event_id, fbp, … }).

v2.10.0

Reporta conversiones a Meta desde la API

Ya puedes reportar conversiones a Meta (Conversions API) por nuestra API pública, con el mismo criterio que aplica el Met: una compuerta valida atribución del anuncio, taxonomía y corroboración antes de emitir. Tú mandas el hecho; nosotros decidimos y emitimos.

  • POST /api/v1/conversions — scope conversions:write.
  • Body: contact_id (el contacto de _contacts), event_name (debe estar en la taxonomía del workspace), y opcionales value, currency, evidence, bucket.
  • Devuelve la decisión real: accepted (encolado para emisión), pending_corroboration (esperando el hecho objetivo), discarded (con la razón: sin atribución, fuera de taxonomía, etc.) o duplicate.

Para WhatsApp, Meta exige nombres de evento de *business messaging* (ej. Purchase, LeadSubmitted, InitiateCheckout) — no los del Pixel web. Este origen (api) puede confirmar Purchase directamente, porque representa un hecho objetivo de tu sistema.

Soporta Idempotency-Key. Es una key de efecto externo real: las keys met_test_ no pueden emitir (evita eventos reales a Meta desde pruebas).

v2.24.0

La recarga automática ahora tiene techo mensual

Qué cambió

La recarga automática de Energía puede tener un tope de gasto por mes. Mientras el tope no se alcance, todo funciona igual; al alcanzarlo dejamos de cobrar la tarjeta y la recarga se reanuda sola el primer día del mes siguiente.

GET /billing/auto-recharge trae tres campos nuevos:

{
  "enabled": true,
  "threshold_usd": 5,
  "amount_usd": 50,
  "monthly_cap_usd": 200,
  "month_spent_usd": 150,
  "capped": false
}
  • monthly_cap_usd — el techo, en USD. null significa sin tope, que es como sigue configurada toda cuenta que no lo haya cambiado.
  • month_spent_usd — cuánto lleva gastado la recarga automática este mes. Una recarga hecha a mano no consume el tope. El mes corta en la zona horaria del workspace y el contador vuelve a cero solo.
  • capped — true cuando la siguiente recarga ya no cabe en el tope. Ojo con esto: no es lo mismo que haber gastado el tope entero. Con un tope de 200, 180 gastados y recargas de 50, capped ya es true aunque queden 20 sin usar.

Qué tienes que hacer

Nada, si no lees esta configuración. Los tres campos vienen siempre, y las cuentas sin tope los reportan como null, 0 y false.

Si muestras el estado de la recarga automática, capped merece su propio mensaje. Por dentro se parece a una recarga pausada —no se cobra—, pero no es una falla: es el límite que la propia cuenta pidió. Tratarlo como error manda a revisar una tarjeta que está perfecta.

if (config.capped) {
  // Llegó al tope del mes. Se reanuda sola; no hay nada roto.
} else if (config.status === 'failed') {
  // Esto sí: la tarjeta rechazó el cobro.
}

Por qué te conviene mirarlo ahora

Sin tope, activar la recarga automática es autorizar un cobro sin techo: si algo consume Energía en bucle, la tarjeta se cobra tantas veces como haga falta y la cuenta se entera por el extracto. Es la razón por la que mucha gente la deja apagada — y con ella apagada, los Mets se detienen cuando el saldo se acaba. El tope es lo que permite dejarla encendida sin firmar un cheque en blanco.

v2.23.0

El destinatario de WhatsApp ya no siempre es un teléfono

Qué cambió

WhatsApp está habilitando los usernames: una persona puede elegir un nombre de usuario y conversar contigo sin darte su número. Cuando eso pasa, Meta deja de enviar el teléfono y en su lugar manda un identificador propio, el *business-scoped user ID* (CO.13491208655302741918), que es específico de tu cuenta: el mismo cliente tiene otro identificador distinto en otro negocio.

Por eso el dry-run de plantillas —POST /channels/{channelId}/whatsapp/send-template con dry_run=true— tiene un campo nuevo:

{
  "dry_run": true,
  "would_send": {
    "recipient": "CO.13491208655302741918",
    "phone_number_id": "109876543210987",
    "template": { "name": "recordatorio_cita", "language": "es" }
  }
}

would_send.to sigue siendo el teléfono y llega igual que siempre para los contactos que lo tienen. Cuando el contacto llegó por username, to no viene y en su lugar está recipient. Siempre recibes uno de los dos, nunca los dos a la vez.

Qué tienes que hacer

Si lees would_send.to para mostrarlo o registrarlo, deja de asumir que existe:

const destino = would_send.to ?? would_send.recipient;

Nada más cambia. Los envíos reales siguen funcionando igual y no tienes que pasar el identificador a mano en ninguna llamada: Met resuelve solo a quién le habla, según lo que Meta nos haya dado de ese contacto.

Por qué te conviene mirarlo ahora

Si guardas a tus clientes usando el teléfono como llave, esa llave va a faltar para quien adopte un username — y ahí no vas a poder reconocerlo ni volver a contactarlo. Conviene que tu sistema empiece a guardar también el identificador y que no exija el número para crear un registro.

v2.24.0

Los conteos de bandeja responden por lo que estás mirando

GET /contacts/counts ahora acepta filtros

Hasta hoy el endpoint devolvía siempre los totales del workspace. Si querías saber cuántos chats abiertos tiene una asesora, el conteo no te servía: había que traer la lista y contarla en tu código.

Ahora acepta los mismos parámetros que GET /contacts — q, status, assigned_to, agent_group, channel_id, tag, unread, autopilot y filters — y devuelve los conteos facetados.

Facetado quiere decir que cada bloque se calcula con todos los filtros activos menos el suyo:

GET /contacts/counts?assigned_to=42&status=open
  • Los cinco contadores de estado (open, pending, human_requested, finished, archived) cuentan los chats de la asesora 42, ignorando status.
  • Los de asignación (total, mine, unassigned, byUser) cuentan los chats abiertos, ignorando assigned_to.
  • byGroup ignora agent_group; byChannel ignora channel_id.

Un bloque que se aplicara su propio filtro daría cero en todas las demás opciones, y quien lo lee se quedaría sin forma de saber qué hay del otro lado.

Un campo nuevo: scopeTotal

{ "total": 287, "scopeTotal": 164, "open": 5, "...": "..." }

scopeTotal es el universo de la carpeta que elegiste — el asesor, el grupo o el canal — sin estado, búsqueda ni filtro avanzado. Es el denominador honesto de un "viendo N de M": total cambia con cada filtro que agregas, así que no sirve para decir de cuánto estás viendo una parte.

Qué no cambia

Si llamas al endpoint sin parámetros, la respuesta es la de siempre: los totales del workspace. scopeTotal es un campo nuevo que se agrega, no reemplaza a ninguno, y los conteos siguen respetando la visibilidad de quien consulta.

El contrato y la referencia ya están actualizados.

v2.23.0

El pipeline de oportunidades tiene una etapa nueva, Negociación

NEGOCIACION, entre Propuesta y Ganado

El campo stage de una oportunidad acepta un valor más: NEGOCIACION. Va justo después de PROPUESTA y antes de GANADO.

curl -X POST https://api.partners.meteor.com.co/leads \
  -H "Authorization: Bearer $MET_API_KEY" \
  -d '{"company_name":"Acme","stage":"NEGOCIACION"}'

El embudo completo queda así: NUEVO → CONTACTADO → CALIFICADO → PROPUESTA → NEGOCIACION → GANADO, con PERDIDO al costado y ACTIVADO al final, que sigue siendo terminal y la escribe Meteor al vincular la oportunidad con su cuenta.

Antes, todo lo que pasaba entre mandar la cotización y firmar vivía en PROPUESTA. Con las dos cosas en la misma etapa, el conteo no distinguía la oportunidad que espera trabajo tuyo de la que espera la firma del cliente.

Qué hacer con tu integración

Es un valor nuevo, no un cambio de los que ya usabas: nada de lo que hoy envías deja de funcionar, y ninguna oportunidad cambió de etapa sola. Si tu código tipa stage como una lista cerrada de valores, regenera tu cliente o suma NEGOCIACION a mano antes de leer oportunidades que ya estén ahí.

npm install @meteor.ia/sdk@latest
v2.22.0

La referencia ahora dice qué es cada grupo y lleva a su guía

Cada operación tiene un enlace a la guía que la explica

Hasta hoy el viaje era de ida: las guías enlazaban a la referencia, y la referencia no devolvía a ninguna parte. Si llegabas a POST /items buscando cómo se usa de verdad, no tenías desde dónde saltar.

Ahora las 300 operaciones y los 36 grupos del contrato llevan externalDocs con el enlace a su guía. No es un cambio del portal: está en openapi.public.json, así que también lo ve tu cliente generado y cualquier herramienta que lea el contrato.

Además, los grupos dejaron de ser encabezados mudos. El documento declaraba tags: [] — la referencia mostraba Slugs, Item Content o handoff sin una línea que dijera qué son. Ahora cada uno trae su descripción, y los tres cuyo nombre técnico no se lee bien tienen nombre visible (agent-groups → Grupos de Mets).

El contrato dice contra qué versión estás generando

info.version estaba clavado en 1.0.0 desde el primer día. Ahora lleva la versión del changelog, así que el número que ves en el contrato es el mismo que encabeza la entrada donde se explica qué cambió.

curl -s https://developers.meteor.com.co/public/openapi.public.json | jq .info.version

Guárdalo el día que generes tu cliente: comparar contra el changelog te dice exactamente qué pasó en el medio.

Versionado y deprecación, escritos

Nueva guía: Versionado y deprecación. Dice qué cambios puedes esperar sin aviso (y cómo escribir tu integración para que no te rompan), cuáles nunca ocurren sin un ciclo previo, y qué pasa exactamente cuando un endpoint se va a retirar.

Lo concreto: mientras un endpoint deprecado siga vivo responde normal, pero cada respuesta trae Deprecation: true, un Sunset con la fecha de retiro y un Link con la alternativa. Si registras los headers de tus llamadas salientes, esa es la forma más barata de no enterarte tarde. Hoy no hay ningún endpoint deprecado.

v2.21.0

El contrato dejó de listar schemas que ninguna operación usa

144 schemas de menos, ninguna operación de menos

El contrato público declaraba 504 schemas, pero solo 360 estaban referenciados por alguna de las 300 operaciones. Los otros 144 eran tipos que ninguna ruta pública usa: llegaban al documento porque estaban definidos en el mismo servidor, no porque formaran parte de la API.

Ahora el contrato solo incluye lo que alguna operación alcanza.

Si generas un cliente desde el contrato, esto se nota. Un generador emite una clase, un tipo o un modelo por cada schema del documento — con lo que hasta ayer tu SDK generado traía 144 tipos que no podías usar para nada, porque ningún endpoint los recibe ni los devuelve. Ese ruido desaparece.

Todo lo demás queda igual y se verificó explícitamente: las 300 operaciones siguen ahí, con su schema de respuesta y con los cuatro errores comunes (400, 401, 429, 500) que se documentaron en la 2.19.0.

Qué no cambia

Ningún endpoint, ninguna ruta, ningún campo de ninguna respuesta. Si ya generaste tu cliente y no usabas esos tipos —no podías—, regenerarlo no te va a romper nada.

El contrato y la referencia ya están actualizados.

v2.20.0

La referencia ya dice qué devuelve cada endpoint

298 de 300 operaciones con su respuesta documentada

Hasta hoy la referencia te decía qué falla, qué acepta y qué es obligatorio, pero no qué contesta. Para saber la forma de una respuesta había que llamar al endpoint y mirar el JSON.

Ahora cada operación declara su schema de salida: los campos, sus tipos y cuáles pueden venir en null. Se ve en el "Try it" de la referencia y, sobre todo, lo usan los generadores de clientes — si generas tipos desde el contrato, ahora salen completos en vez de unknown.

Las dos que no lo declaran es porque no devuelven JSON: GET /events/stream es un stream text/event-stream y GET /item-files/{fileId}/download son los bytes del archivo. Las dos dicen eso mismo en la referencia.

Los schemas abiertos siguen siendo abiertos, y ahora se nota. Un contacto, un ítem o una fila de colección llevan los campos personalizados de tu workspace, así que su schema declara los campos garantizados y marca explícitamente que pueden venir más. Antes esa distinción no estaba escrita en ninguna parte y un cliente generado podía descartar tus campos propios sin avisar.

Los POST documentan 201, que es lo que responden

Si creas algo con un POST —una tarea, un contacto, un ítem, un webhook— la API responde 201 Created. La referencia decía 200 en varias de esas operaciones.

Si tu integración valida el código de estado, revisa que aceptes 201 y no solo 200. El comportamiento del API no cambió: siempre respondió 201; lo que se corrigió es lo que decía la documentación.

Dos precisiones que evitan un error común

  • Los mensajes de un contacto piden conversations:read, no contacts:read. Una key con permisos de contactos lista contactos pero no lee sus conversaciones. La guía de contactos decía lo contrario.
  • Un recordatorio le llega al contacto, como plantilla de WhatsApp aprobada. No existe el modo "nota interna": el campo message es el texto que va a leer el contacto y note es el único que no se envía. Está dicho ahora en la referencia de POST /reminders.