Changelog

Novedades de la API

Cada cambio de la superficie pública queda registrado aquí, del más nuevo al más viejo.

v2.39.0

El listado de contactos deja de cargar con dos campos de configuración interna

GET /contacts devolvía 573 KB por página de 100 contactos, y el 80,7% de ese peso eran dos claves que el listado no usa:

clave% de la respuesta
$followup_rules60,5%
$ad_referral20,2%

Las dos salen del listado. El detalle no cambia: GET /contacts/{id} las sigue devolviendo completas.

Por qué pesaban tanto

$followup_rules es la configuración de seguimientos que un flujo sincroniza en cada contacto para poder revalidarla cuando dispara. En el workspace que medimos, los 194 contactos tenían exactamente la misma versión — unos 3,5 KB repetidos fila por fila. Un listado de 100 contactos publicaba esa misma configuración cien veces.

$ad_referral es el texto completo del anuncio del que llegó el contacto. Útil al abrir la conversación; no al listar.

Qué vas a notar

La respuesta pesa alrededor de una quinta parte de lo que pesaba. En la medición contra la colección más grande, la consulta bajó de ~510-710 ms a ~265 ms, que es el piso de la red: el listado ya no depende del peso de cada contacto.

Si leías alguno de los dos desde el listado

Pídelos en el detalle. Un barrido que necesite $followup_rules de muchos contactos ahora hace una llamada por contacto — es más trabajo, y es deliberado: esa configuración es del flujo, no del contacto, y sacarla del listado es lo que lo vuelve rápido para todos los demás.

El resto de los campos no cambia. Los de identidad —$name, $phone, $email, $status, $tags— y todos tus campos personalizados siguen viajando igual.

De dónde salió esto

De medir el tráfico real de la API: GET /contacts era el segundo endpoint más usado de la plataforma y el más lento de los que se usan de verdad. Es el mismo criterio que ya se había aplicado en agosto a las respuestas de las herramientas del MCP, donde estas dos claves salieron por la misma razón.

v2.38.0

La referencia de Partners deja de ser una lista de 37 filas

La API de Partners publica 37 operaciones y hasta hoy la referencia las mostraba casi todas al mismo nivel: GET /partner/clients y POST /partner/projects/{id}/milestones/{milestoneId}/complete una debajo de la otra, sin nada que dijera cuál era cuál.

Ahora quedan en siete grupos, cada uno con una línea que explica qué se puede hacer ahí y un enlace directo a su sección del portal:

GrupoOperacionesScope
Clientes1partner:clients:read
Comisiones1partner:commissions:read
Liquidaciones2partner:payouts:read
Oportunidades11partner:leads:*
Proyectos15partner:projects:*
Tickets6partner:support:*
MCP1mcp:use

Los grupos coinciden con los scopes

No es casualidad ni estética: lo que ves en un grupo es exactamente lo que una key de ese dominio puede hacer, ni más ni menos. Si tu key tiene partner:leads:*, el grupo «Oportunidades» es tu superficie completa.

Comisiones y Liquidaciones quedan separados aunque sumen tres operaciones entre los dos. Juntarlos por «son pocos» habría hecho que el grupo prometa algo que la key no alcanza.

El orden no es alfabético

Abre con las cuatro operaciones de solo lectura que contestan «¿cuánto llevo y de quién?» — es lo que se puede probar sin escribir nada. Después el pipeline, que son las 26 operaciones donde está toda la escritura. Después soporte. MCP cierra, porque es un endpoint JSON-RPC y no un recurso REST.

Y las dos referencias ahora se ven igual

El contrato de Met también declara sus grupos (x-tagGroups), así que la barra lateral se organiza igual en las dos referencias. Sus 27 grupos quedan repartidos en seis secciones: Empezar, Datos, CRM y canales, Automatización, Workspace y Partners.

Ninguna operación cambió de nombre, de ruta ni de forma. Si generas tu cliente del contrato, esto no te toca nada.

v2.37.0

El contrato ya declara el sobre en el que viaja toda respuesta

Toda respuesta correcta de la API viaja dentro de un sobre: no llega el recurso pelado, llega envuelto en {"success": true, "data": …}. Siempre fue así. Lo que no era así es el contrato OpenAPI, que describía el recurso sin el sobre.

{ "success": true, "data": { "object": "identity", "livemode": true } }

La API no cambió. Ni un endpoint, ni un campo, ni un código de estado. Lo que cambió es el papel, que ahora dice lo que la API venía haciendo.

A quién le importa esto

Si usas los SDK oficiales de TypeScript o Python: a ti no. Te entregan el contenido de data y convierten el sobre de error en una excepción. No tienes que hacer nada.

Si generas tu cliente del contrato: regenéralo. El cliente que salía hasta hoy buscaba el recurso en la raíz de la respuesta; el que sale ahora lo busca en data, que es donde viaja. Con eso la forma cuadra con lo que la API responde.

Si ya habías parchado tu cliente generado a mano para leer .data, regenerar te va a devolver ese parche como redundante — bórralo.

Si llamas con curl o con un cliente propio: nada cambia, pero ahora la referencia te muestra la forma correcta en vez de la incompleta.

Lo que cambió, en números

334 de 353 operaciones públicas —298 de Met y 36 de la API de Partners— pasaron a declarar el sobre en sus respuestas 2xx. Las 19 restantes no lo llevan y tampoco lo declaran, que es lo correcto:

  • Seis envíos de WhatsApp (/channels/{id}/whatsapp/send-*) devuelven success en la raíz sin data. Su forma no cambió.
  • POST /mcp, en Met y en Partners, responde el sobre JSON-RPC que define la especificación de MCP, que es otro.
  • Las respuestas sin cuerpo JSON: los 204, el stream de eventos (text/event-stream) y la descarga de archivos.

La paginación va afuera de data

Una lista paginada pone pagination como hermana de data, no adentro:

{
  "success": true,
  "data": [ { "id": 9812 } ],
  "pagination": { "page": 1, "limit": 50, "total": 128, "pages": 3 }
}

Eso también era así antes y tampoco estaba declarado. Ahora sí.

Dónde leerlo

La guía de errores e idempotencia abre con una sección nueva sobre la forma de una respuesta, con el sobre de éxito y el de error uno al lado del otro.

v2.36.0

La tabla de scopes decía menos de lo que la API aplica

La tabla de scopes de la guía de autenticación se mantenía a mano y se había quedado atrás del código. Ahora se genera del catálogo de scopes cruzado con el contrato OpenAPI, y hay un chequeo que falla si vuelve a separarse.

Lo que hay que revisar si usas keys de prueba

El párrafo de modo de prueba listaba cuatro scopes bloqueados. Son siete. Los tres que faltaban son justamente los que tienen efecto sobre dinero:

conversions:write        emite un evento real a Meta
workspaces:recharge      inicia un cobro real en Stripe
partner:billing:manage   deja una tarjeta en archivo

La API siempre los bloqueó —una key met_test_ recibe 403 con test_mode_restricted—, así que no cambió ningún comportamiento. Lo que estaba mal era la guía: decía que una llave de prueba llegaba a sitios a los que nunca llegó. Si diseñaste tu integración creyendo eso, revisa esa parte.

Seis scopes que la tabla no mencionaba

Faltaban conversions:read, conversions:write, rag:read, rag:write, workspaces:recharge y partner:billing:manage. Ya están, los 57.

La tabla ahora trae una columna Endpoints con cuántas operaciones piden cada scope, y distingue tres situaciones que antes se veían iguales:

  • Con endpoints: el número.
  • Sin endpoints pero con eventos detrás: conversions:read no lo pide ninguna operación REST, pero sin él no puedes suscribirte a conversion.sent ni a conversion.discarded. Antes la guía decía que no habilitaba nada.
  • Retirados del panel: rag:read, rag:write y sites:read ya no se ofrecen al emitir una key ni al registrar una app OAuth.

sites:read sale de la vitrina

Nunca tuvo endpoints detrás: marcarlo no habilitaba ninguna llamada. Deja de aparecer entre los scopes que puedes pedir.

Las keys que ya lo tengan guardado siguen funcionando igual. El scope sigue siendo válido y no invalida nada; simplemente no se ofrece de nuevo. Es el mismo criterio que se aplicó a rag:read y rag:write.

Guía nueva: Automatizaciones

Doce operaciones que estaban solo en la referencia ahora tienen narrativa: qué disparadores existen y qué condiciones exige cada uno, cómo se programa una recurrencia y en qué zona horaria corre, cómo se edita una automatización gestionada sin pisar un cambio ajeno (expected_revision), y por qué un run-now sobre un flujo devuelve un run que no aparece después en el listado.

Probar una habilidad ya no alcanza ítems ajenos

POST /workspaces/{id}/skills/{skillId}/test aceptaba cualquier identificador de ítem de la plataforma, y el nombre del ítem encontrado volvía dentro de la respuesta. Ahora la búsqueda queda acotada al catálogo de habilidades: un identificador que no sea de una habilidad responde «no existe», sin distinguir entre lo que no está y lo que no es tuyo.

v2.35.0

Ya puedes preguntar quién eres, y la lista de eventos dejó de ser prosa

Dos huecos del mismo tipo: cosas que la API sabía y no había forma de preguntarle. Ninguno cambia comportamiento existente.

GET /me — con qué identidad entra tu key

Antes no existía introspección. Para saber si una key servía había que llamar a algo real y deducir por el código de error: 401 la key está mal, 403 le falta el scope, 2xx anda. Eso responde "¿sirve?", nunca "¿quién soy?" — el workspace y el entorno salían de lo que tuvieras configurado de tu lado, no de lo que el servidor resolvió.

GET /api/v1/me
{
  "object": "identity",
  "livemode": true,
  "api_key": {
    "object": "api_key",
    "id": "key_01HXYZ",
    "owner_type": "workspace",
    "env": "live",
    "scopes": ["items:read", "runs:read"],
    "rate_limit_rpm": 120,
    "monthly_quota": null
  },
  "workspace": { "object": "workspace", "id": 7, "slug": "acme" },
  "partner": null
}

rate_limit_rpm y monthly_quota son los vigentes: el más estricto entre lo que dice tu key y lo que dice el plan del workspace. Es el número con el que te vas a encontrar en un 429, y descubrirlo a golpes es peor que leerlo.

El secreto de la key no sale, ni nada con qué reconstruirlo: ni prefijo, ni los últimos caracteres, ni la longitud. Lo único que la identifica es su id, el mismo que ves en el panel y el que queda en los registros de request — sirve para pegar en un ticket sin ser una credencial.

Hoy pide el scope runs:read. Es una limitación conocida y es la que menos quita: es exactamente el scope que había que probar antes para simular esto, así que ninguna key quedó con menos alcance del que ya tenía.

En los SDKs es met.me.retrieve(), en TypeScript y en Python.

met whoami dejó de adivinar

El comando ya no deduce: pregunta. Y como ahora el servidor le dice cuál es tu workspace, aparece un caso que antes era invisible — si tu configuración local apunta a un workspace y la key opera otro, te lo avisa en vez de imprimir el tuyo con un visto bueno. met whoami --save-workspace guarda el que resolvió el servidor.

Los 17 eventos de webhook, en el contrato

enabled_events documentaba su lista en un párrafo y validaba contra otra en el código. Ahora las dos salen de la misma constante: el catálogo de eventos es la fuente, el contrato publica el enum y el validador usa esos mismos valores. No pueden separarse.

Si generas tu cliente del contrato, enabled_events pasa a traer la lista cerrada de valores válidos —los 17 más *— en vez de un string libre.

v2.34.0

El modelo de un Met se pide por tier, no por id

Al crear o actualizar un Met, el campo model ahora se documenta con los tres tiers y nada más:

PATCH /agents/{id}
{ "model": "brillante" }

Si hoy mandas un id de modelo, tu integración sigue funcionando. No hay 400 nuevo. Lo que cambia es qué queda guardado: un id se resuelve a su tier y el Met queda con el modelo vigente de ese tier, no con el que nombraste.

Por ejemplo, "claude-sonnet-4-5" deja un Met en el Brillante de hoy.

Por qué

Un id de modelo es nuestro, no tuyo. Fijar uno dejaba a ese Met afuera de cada mejora que hacemos —más barato, más rápido, mejor— en silencio y sin fecha de vencimiento. Nos pasó de verdad: dos Mets quedaron dos generaciones atrás mientras el resto de la flota avanzaba, y nadie se enteró hasta que fuimos a mirar.

Con el tier eliges cuánta cabeza quiere tu Met. Qué modelo la provee es problema nuestro, y es el tipo de problema que preferimos resolver sin pedirte que cambies código.

Qué hacer

Si guardas ids en tu configuración, cámbialos por agil, brillante o genio. La respuesta de un agente ya trae tier junto a model, así que puedes leer de ahí lo que necesites mostrar.

Los ids se seguirán aceptando durante la ventana de deprecación. Cuando cierre, avisaremos aquí antes de que empiecen a rechazarse.

v2.34.0

Las credenciales salen de dos respuestas, y una lista deja de ser global

Dos endpoints de lectura estaban devolviendo credenciales guardadas, y uno de ellos devolvía además datos de workspaces que no eran el tuyo. Los dos quedaron acotados hoy, sin ciclo de deprecación: no se le da seis meses de aviso a una respuesta que nunca debió llevar ese campo.

GET /skills/all ahora es de tu workspace

Antes leía la tabla entera sin filtrar por workspace. Ahora devuelve solo las habilidades activas de la cuenta a la que pertenece tu API key, y ya no incluye el objeto credentials.

Si tu integración contaba las filas de esta respuesta, va a contar menos. Es el arreglo, no una regresión: las filas que faltan nunca fueron tuyas.

GET /mcps/active ya no trae los valores de las credenciales

La respuesta pasó de ser la fila cruda de configuración a una proyección explícita. Lo que sigue viajando es mcp_data.credentials_required —los nombres de las credenciales que cada conexión necesita, que es lo que se usa para pintar un formulario— y lo que deja de viajar son los valores.

Un scope de lectura como integrations:read no debería alcanzar un secreto, y alcanzaba.

Las habilidades del workspace tampoco las devuelven

Cinco respuestas del catálogo de habilidades venían trayendo el objeto credentials de cada configuración:

GET  /workspaces/{id}/marketplace/skills     ← dentro de workspace_config
GET  /workspaces/{id}/skills/active
POST /workspaces/{id}/skills/{skillId}/activate
POST /workspaces/{id}/skills/{skillId}/deactivate
PUT  /workspaces/{id}/skills/{skillId}/credentials

La primera es la que más incomoda: skills:read es un permiso de lectura y alcanzaba un secreto. La última hacía eco de lo que acabas de guardar, que tampoco hace falta — ya lo tienes tú, y la respuesta la ve todo lo que esté en el camino.

Aquí el contrato no cambia: WorkspaceSkillConfigDto nunca declaró ese campo. Lo que cambia es que ahora la respuesta se parece al contrato.

Los enlaces entre ítems validan a qué workspace pertenecen

GET /items/{itemId}/links, POST /items/{itemId}/links y DELETE /item-links/{linkId} ahora verifican que el ítem sea de tu workspace antes de responder. Un id ajeno devuelve 404, no 403: la API nunca confirma que exista algo que no puedes ver.

En el POST se validan los dos extremos del enlace. Es deliberado: la lectura embebe el ítem destino, así que un enlace apuntando fuera de tu workspace habría convertido cada lectura posterior en una fuga.

Si generas tu cliente del contrato

Los schemas ActiveSkillResponseDto y ActiveMcpResponseDto pasaron de abiertos a cerrados: declaran exactamente los campos que viajan. Si tu generador aceptaba propiedades extra en estos dos, ya no hay ninguna que aceptar.

v2.33.0

Devolverle una cuenta gestionada a tu cliente

Una cuenta que aprovisionaste y venías pagando ahora puede pasar a manos de tu cliente, sin que él pierda nada de lo que tiene adentro:

POST /workspaces/{id}/handover

Desde ese momento la licencia se le cobra a él, tú dejas de pagarla, y esa cuenta vuelve a generarte comisión — el descuento que la reemplazaba termina con el traspaso.

Tres cosas que conviene saber antes de llamarlo:

  • Tu cliente necesita su propio medio de pago guardado. Si no lo tiene, la llamada responde 409 y no cancela nada. Es a propósito: soltar la cuenta antes de que él pueda pagarla la dejaría sin servicio, y el que se queda sin Met es él, por algo que no pidió.
  • La energía que le cargaste se queda con la cuenta. Ya es servicio entregado; no se descuenta ni se devuelve.
  • Se puede reintentar sin miedo. Si la llamada se corta a mitad de camino, repetirla termina el traspaso en vez de crear una segunda suscripción.

Tu cliente también puede pedirlo desde su propia facturación, sin pasar por ti. No es un descuido: una cuenta gestionada sin salida propia sería una cuenta de la que él no puede irse.

v2.32.0

Cargar energía sin abrir un enlace de pago

POST /workspaces/{id}/recharge acepta off_session: true y cobra directo a tu medio de pago guardado, sin devolver un enlace que alguien tenga que abrir:

POST /workspaces/42/recharge
{ "amount_usd": 100, "off_session": true }

→ 200 { "object": "energy_charge", "status": "succeeded",
        "payment_intent_id": "pi_…", "acreditado": false }

acreditado viene en false a propósito, incluso cuando el cobro salió bien. El saldo lo acredita el webhook del pago, que es el único que sabe que el dinero entró de verdad; sumarlo en esta respuesta daría saldo por un cobro que todavía se puede revertir. Si necesitas confirmar, consulta el saldo de la cuenta unos segundos después.

Sin la bandera todo sigue igual: devuelve el enlace de pago de siempre, así que nada de lo que ya tengas escrito cambia. Y si pides off_session sin medio de pago guardado, responde 409 diciendo cuál es la salida.

v2.31.0

Pasar una cuenta aprovisionada a un plan, cobrado a tu medio de pago

Si aprovisionas cuentas para tus clientes, ahora puedes pasarlas de Tech Partner a un plan comercial y que la licencia se cobre a tu tarjeta:

POST /workspaces/{id}/subscription
{ "plan_slug": "estudio", "billing_cycle": "monthly" }

La respuesta trae el descuento aplicado, su duración y el nivel con el que se calculó.

El descuento reemplaza tu comisión, no se suma a ella. Es el espejo exacto de lo que habrías cobrado: el mismo porcentaje, durante el mismo tiempo. Si tienes una tasa negociada, se usa esa y no la de tu nivel. Por esas cuentas no vas a ver comisión, y en el ledger queda dicho por qué.

Dos cosas que conviene saber antes de llamarlo:

  • Necesitas un medio de pago guardado. Sin él responde 409: una suscripción sin con qué cobrarla nace impaga, y el que sufre ese estado es tu cliente, que no puede resolverlo porque la factura no es suya.
  • Una cuenta solo puede estar gestionada una vez. Un segundo intento responde 409 en vez de duplicar el descuento.

La energía sigue cobrándose al margen del plan del cliente, así que pasar una cuenta a un plan también le baja lo que consume — que suele ser la razón real para hacerlo.

v2.30.0

Un Tech Partner ya puede dejar su tarjeta en archivo

Si aprovisionas cuentas para tus clientes, ahora puedes guardar un medio de pago a tu nombre y dejar de abrir un enlace de cobro cada vez.

POST /partner/payment-method/setup
{ "success_url": "https://tu-panel.com/ok", "cancel_url": "https://tu-panel.com/no" }

→ 200 { "checkout_url": "https://checkout.stripe.com/…", "session_id": "cs_…" }

El enlace abre un Checkout en modo setup: no cobra nada, solo deja la tarjeta guardada. Por tu integración nunca pasa un dato de tarjeta.

Para saber qué hay guardado, GET /partner/payment-method responde la marca, los últimos cuatro dígitos, el vencimiento y si ya venció. Se lee de la fuente en cada llamada, así que si cambias la tarjeta el cambio se ve enseguida.

Los dos van con el scope nuevo partner:billing:manage, aparte de workspaces:provision a propósito: una key que le entregas a un integrador para que te cree cuentas no debería poder, con el mismo permiso, cambiar la tarjeta con la que pagas todo. Como cualquier scope que mueve dinero, no funciona en modo test.

v2.29.0

Seis recetas de punta a punta y la documentación servida en markdown

Seis recetas nuevas arman un caso completo, de la primera línea a producción, con el código listo para copiar: un agente que contesta WhatsApp con tu lógica en el medio, sincronizar un sistema externo con colecciones, recibir eventos firmados en tu backend, ejecutar un Met desde GitHub Actions, mostrar un run en vivo en tu UI y una tarea que pide aprobación humana. Cada una declara con qué se arma, qué partes de la API toca y qué scopes necesita tu key.

La documentación también se sirve en markdown, para cuando quien la lee es tu agente. Cambia .html por .md en cualquier guía, o pide Accept: text/markdown sobre la misma URL y recibes el texto sin la página alrededor. llms.txt trae el mapa del portal y llms-full.txt las guías completas concatenadas.

Cada guía muestra ahora cuándo se actualizó por última vez, se puede copiar entera como markdown desde su encabezado, y todos los bloques de código del portal tienen botón de copiar.

"meteor-api: un pack de skills para los agentes que escriben código"

El servidor MCP entrega las herramientas; lo que le faltaba a un agente que programa contra Meteor era el criterio de cuál usar, en qué orden y qué decide el servidor por él. meteor-api es ese criterio, empaquetado en seis sub-skills —Mets, CRM, tareas, datos, automatizaciones y plantillas— que se instalan en .agents/skills/ y sirven en cualquier cliente que lea esa carpeta:

curl -fsSL https://developers.meteor.com.co/skill-pack/install.sh | sh

El pack se arma del mismo catálogo de herramientas que expone el servidor MCP y de las guías publicadas, así que dice lo mismo que la referencia.

Se llama meteor-api y no skills para no confundirlo con las Skills del producto, que son otra cosa: capacidades que activas y vinculas a un Met.

Además, catorce herramientas del servidor MCP ahora explican lo que antes había que descubrir usándolas: qué límite tiene la respuesta, qué campos vienen recortados, cuál es la herramienta que sigue y cuándo una operación debita Energía.

v2.28.0

Procesos administrados visibles y controlables desde Automatizaciones

La API pública de Automatizaciones ahora refleja los procesos administrados por el sistema junto con las tareas, flujos y campos de IA del workspace. Una integración puede distinguirlos mediante is_system_managed, consultar su última ejecución y, cuando el proceso lo permite, ejecutarlo manualmente, revisar su historial o ajustar su horario seguro.

Los nuevos controles conservan los scopes granulares de Automatizaciones y no permiten editar la lógica interna del proceso administrado.

Creación de cuentas de Met desde tu producto

  • La API pública incorpora POST /workspaces para crear una cuenta de Met con su dueño. Es exclusivo de las llaves de partner con el permiso workspaces:provision, disponible para los Tech Partners aprobados.
  • La cabecera Idempotency-Key es obligatoria en esta operación: reintentar con la misma clave devuelve la cuenta que ya se creó, no una nueva.
  • POST /workspaces/{workspaceId}/recharge (permiso workspaces:recharge) abre un enlace de pago para cargarle energía a una cuenta que creaste. Solo funciona sobre tus propias cuentas.

La cuenta nace con el plan Tech Partner y sin energía, así que la respuesta incluye operational: false mientras el saldo esté en cero. La energía la paga siempre la cuenta que la consume: carga saldo antes de que tu cliente empiece a usarla. La recarga no acredita el saldo al instante — devuelve un enlace de pago y la energía entra cuando el pago se completa. El dueño recibe por correo un enlace para definir su contraseña — nunca la defines tú.

Cada nivel de partner tiene un cupo mensual de cuentas. Al agotarse, la operación responde 403 indicando el cupo, el consumo del mes y cuándo se reinicia.

Cancelación de flujos y contrato actualizado de Red Máster

  • La API pública incorpora POST /workspaces/{workspaceId}/flow-runs/{runId}/cancel para detener el agendado de nuevos nodos y marcar una ejecución como cancelada.
  • El contrato de Partners refleja la terminología y las operaciones vigentes de Red Máster, incluidas las solicitudes, vinculaciones y designaciones.

Cancelar una ejecución no interrumpe una generación que el proveedor ya haya comenzado; esa operación termina y conserva su cobro correspondiente.

v2.27.0

La API de conversiones documenta sus errores de operación

Las operaciones públicas de conversiones de contacto y conversiones web ahora documentan los errores 403, 404 y 409 que pueden devolver. Las integraciones pueden distinguir permisos insuficientes, recursos no disponibles y conflictos o duplicados sin depender de respuestas no documentadas.

La API de conversiones documenta sus errores de acceso y conflicto

Las operaciones POST /conversions y POST /conversions/web ahora describen en OpenAPI las respuestas de error que una integración puede recibir:

  • 403 cuando la credencial no tiene acceso a la operación.
  • 404 cuando el recurso asociado no existe dentro de su alcance.
  • 409 cuando la conversión entra en conflicto con el estado existente.

Este ajuste no cambia el formato de las solicitudes ni de las respuestas; hace que la referencia y los clientes generados reflejen el contrato vigente.

v2.26.0

La API de Partners ya documenta respuestas y errores de acceso reales

Las 37 operaciones de met.partner.* ahora describen su respuesta exitosa: clientes, comisiones, payouts, oportunidades, proyectos, hilos y tickets. El OpenAPI deja de mostrar respuestas vacías, así que la referencia y los clientes generados pueden conocer la forma curada de cada recurso.

También documentamos los errores que una integración puede recibir de verdad:

  • 403 cuando falta un scope, una capacidad no está habilitada o el recurso queda fuera de la cartera de la key.
  • 404 cuando un recurso no existe dentro de ese alcance, sin revelar recursos de otro tenant.
  • 409 en operaciones que mutan estado, incluidos los conflictos de Idempotency-Key.

Las requests autenticadas con API key devuelven ahora la taxonomía pública de error también cuando una validación o regla de dominio usa una excepción estándar: type, code, message y request_id. Las sesiones del panel conservan su formato anterior.

Acceso API por plan mensual y nuevo plan Tech Partner

  • Las API keys se pueden crear y administrar desde el workspace antes de contratar un plan.
  • Para usar una key, el workspace operado debe tener un plan mensual vigente; también cuentan el trial y los planes bonificados.
  • Las keys de Partner validan el plan del workspace atribuido y ya no pueden operar un cliente sin suscripción activa.
  • Los límites de RPM y cuota mensual se resuelven desde el plan vigente al ejecutar cada key.
  • Tech Partner es un plan restringido de USD 0 de cargo fijo mensual, habilitado únicamente por Meteor para alianzas aprobadas; la Energía se cobra a tarifa premium.

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.
  • cappedtrue 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 /contactsq, 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í: NUEVOCONTACTADOCALIFICADOPROPUESTANEGOCIACIONGANADO, 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-groupsGrupos 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.
v2.19.0

Un agente ya puede agendar un seguimiento y buscar archivos; el CLI suma plantillas y tickets

MCP: 34 herramientas

Agendar un seguimiento. Cuando un agente no puede resolver algo ahora, tenía una sola salida: pasarle el turno a un humano. Ahora tiene la otra — quedar en escribirle al contacto después.

met_create_reminder agenda el mensaje, met_list_reminders muestra los pendientes, met_update_reminder mueve la fecha y met_cancel_reminder lo detiene.

Un recordatorio le llega al contacto por WhatsApp a la hora agendada, como plantilla aprobada; no es una nota interna. El workspace necesita tener configurada su plantilla de recordatorio (Ajustes). Detalle completo en Operación del workspace.

Encontrar un archivo. met_list_assets busca en la mediateca del workspace por nombre o por tipo, y devuelve el enlace. Es lo que hace falta cuando el agente tiene que enviar el catálogo, la lista de precios o una ficha: encuentra el archivo y pasa el enlace en el mensaje.

Subir archivos no se hace por MCP —eso es POST /workspaces/{id}/assets, que recibe multipart— porque pasar un archivo por una herramienta significaría mandar sus bytes codificados en el mensaje del modelo.

CLI 0.5.0

met snapshots install 3f1e… --set negocio="Muebles del Norte"
met partner tickets reply 84 "Ya quedó corregido, prueba de nuevo"

met snapshots monta una plantilla completa en el workspace desde un script o desde CI, que es lo que necesitas al dar de alta un cliente nuevo. list y show <slug> recorren el catálogo —show imprime las variables que pide, con * en las obligatorias—, claim da acceso a las gratuitas e install la instala.

La instalación es aditiva e idempotente y todo llega desactivado. install imprime el id de la instalación en stdout, así que en un pipeline lo guardas y revisas antes de prender nada.

Aquí --set no convierte tipos: un teléfono o un NIT quedan como texto. Si una variable de verdad no es texto, pásala en JSON con --vars '{"cupo":10}'.

met partner tickets es la primera escritura del grupo partner, y la única que va en una terminal: responder soporte se hace de a uno y suele ser urgente. list muestra tus tickets, show <id> imprime el hilo completo y reply <id> "mensaje" responde. Abrir y cerrar un ticket siguen siendo del panel.

npm i -g @meteor.ia/cli
v2.18.0

Si eres partner, ya puedes conectar tu agente a tu cartera por MCP

Tu key de partner ahora abre un servidor MCP con 13 herramientas sobre tu propio negocio: clientes atribuidos, comisiones, liquidaciones, oportunidades, proyectos y tickets.

https://api.partners.meteor.com.co/mcp

Es un endpoint distinto del de Met, y con qué key te autenticas decide cuál usar: una key de workspace habla con el MCP de Met (api.met.meteor.com.co/api/v1/mcp), y una key de partner con este. Los dos objetos —un workspace y una cartera de clientes— no comparten permisos, así que tampoco comparten catálogo.

Tu key necesita el scope mcp:use para abrir la sesión, y cada herramienta pide además su partner:*. Una herramienta cuyo scope no tienes no aparece en el catálogo.

Qué trae

dominioherramientas
Clienteslist_clients
Finanzaslist_commissions, list_payouts
Oportunidadeslist_leads, get_lead, create_lead, list_lead_comments, comment_lead
Proyectoslist_projects, list_project_tasks
Soportelist_tickets, get_ticket, reply_ticket

Todas con el prefijo met_partner_.

Lo que queda fuera, y por qué

Mover una oportunidad de etapa y borrarla están en la API REST y no en el MCP. Cambiar la etapa de un negocio o eliminarlo se decide mirando el tablero, con el contexto de la conversación con el cliente delante — no es algo que convenga delegar a un modelo en medio de un intercambio. Lo mismo con cerrar o reabrir un ticket: puedes responderlo por MCP, el estado se cambia desde el panel.

Y una nota que vale para cualquier herramienta: el partner sobre el que operas sale de tu key, no de un argumento. No hay forma de pedirle a una herramienta que lea la cartera de otro partner.

Ejemplo con Claude Desktop

{
  "mcpServers": {
    "meteor-partners": {
      "type": "http",
      "url": "https://api.partners.meteor.com.co/mcp",
      "headers": { "Authorization": "Bearer met_live_tu_key_de_partner" }
    }
  }
}

Detalle de los scopes y del modelo de keys en Partners API.

v2.17.0

El MCP ya lee conversaciones y pasa el turno a un humano; el CLI suma Mets, consumo y webhooks

MCP: 26 herramientas

Leer la conversación. met_get_messages devuelve los mensajes del hilo con un contacto, del más viejo al más nuevo. Es la contraparte de met_send_message: con las dos, un asistente puede responder sabiendo qué ya se dijo.

Pasarle el turno a un humano. met_set_autopilot prende o apaga la atención automática de un contacto, y met_pause_autopilot la suspende por N minutos y la reanuda sola.

Para una intervención puntual usa met_pause_autopilot. Apagar el autopiloto es permanente hasta que alguien lo prenda, y es fácil que un contacto quede sin atención automática durante semanas. Con minutos, se reanuda solo; con 0, reanuda ya.

Elegir qué Met ejecutar. met_list_agents lista los Mets del workspace con su nombre, que es lo que después pasas como hint a met_run_agent.

Los dominios de configuración —webhooks, automatizaciones, funciones, integraciones— no tienen herramientas MCP a propósito: se configuran una vez desde el panel y no son algo que un agente resuelva en medio de una conversación.

CLI 0.4.0

met agents list              # los Mets del workspace
met billing ejecuciones      # el detalle de consumo, ordenado por costo
met webhooks events 12       # historial de entregas, con el código HTTP

met agents te da el nombre que necesitas para met run --met <nombre>. Y met agents show <id> imprime sus herramientas, que es lo que suele explicar por qué un Met no hace algo: casi siempre le falta la herramienta, no el prompt.

met billing tiene tres vistas. resumen es el agregado del período, ejecuciones el detalle ordenado por costo —donde se ve qué está gastando de verdad— y saldo el estado de la cuenta. saldo avisa además si la recarga automática está apagada: en una integración desatendida, quedarse sin Energía detiene las tareas.

met webhooks complementa met listen. Ese trae los eventos a tu máquina; esto muestra qué intentó entregar Meteor y con qué respondió tu servidor. Si events sale vacío, el webhook no se ha disparado — revisa el disparador antes que tu endpoint. met webhooks sample <id> dispara un evento de prueba.

Es solo lectura y prueba: crear un webhook o rotar su secreto se hace desde el panel, para que el secreto no quede en tu historial de shell.

npm i -g @meteor.ia/cli
v2.16.1

Los tres endpoints de flujos ya declaran qué error devuelven

Nada cambió en el comportamiento del servidor. Esto es la referencia poniéndose al día con lo que la API ya hacía.

Las tres operaciones de importación y exportación de flujos —FlowsController_importFlow, FlowsController_importPreview y FlowsController_exportFlow— ya declaran sus respuestas de error en el spec: 400, 401, 429 y 500, apuntando a los mismos componentes de error que el resto de la API. Antes las devolvían igual y el spec no lo decía, así que un cliente generado desde la referencia no tenía tipo para el caso de falla y quedaba adivinando la forma del cuerpo.

Si usas el SDK, actualízalo y los tipos llegan solos. Si construiste tu cliente desde el spec, vale regenerarlo: son las tres operaciones que más se llaman desde scripts, y 429 en un importador que corre en lote es un caso que conviene manejar.

Y cuatro schemas que salen de components, sin efecto para nadie: CreateDashboardDto, CreateWidgetDto, DemoGreetingDto y QuerySpecDto estaban declarados sin una sola propiedad y sin ninguna operación que los usara. Eran artefactos de generación, no contratos — el mismo caso que WhatsappWabaTargetDto la semana pasada. Si alguno aparece en tu cliente generado, es un tipo vacío que nada devuelve.

El conteo de operaciones no se movió: 300 antes y 300 ahora, ninguna agregada y ninguna retirada.

v2.16.0

La referencia ya muestra qué devuelve cada endpoint, e iteradores para paginar por desplazamiento

Qué devuelve cada endpoint

Los cinco dominios más usados —runs, tareas, contactos, ítems y colecciones— ya declaran la forma de su respuesta en el contrato. Como los campos llevan ejemplos, la referencia te muestra la respuesta sin que necesites una key.

Si generas clientes a partir del OpenAPI, hay una diferencia entre ellos que conviene tener presente:

  • Runs tiene schema cerrado: la lista de campos está completa.
  • Tareas, contactos, ítems y colecciones tienen schema abierto (additionalProperties: true). Esos endpoints devuelven la fila completa, y los campos dependen del esquema de tu workspace y de tus campos personalizados. Se documentan los campos garantizados y se declara explícitamente que puede venir más — así tu cliente generado no descarta lo que no está en la lista.

Paginar por desplazamiento

Si paginas facturación, Mediateca, eventos, entregas de webhook o la búsqueda de ítems, revisa tu código.

Esos endpoints paginan con limit + offset (la búsqueda de ítems, con limit + page). Mandarles starting_after no da error: el parámetro se ignora, la respuesta es 200, y recibes la primera página una y otra vez.

En el SDK, billing.executions() y billing.transactions() ahora reciben offset en vez de starting_after. Si paginabas con starting_after, cámbialo.

Y hay iteradores para no escribir el bucle a mano:

for await (const e of met.billing.iterateExecutions()) { /* ... */ }
for await (const a of met.assets.iterate({ mime_prefix: 'image/' })) { /* ... */ }
for await (const it of met.items.iterateSearch('factura')) { /* ... */ }
for e in met.billing.iterate_executions():
    ...
for a in met.assets.iterate(mime_prefix="image/"):
    ...

Una advertencia del terreno: el desplazamiento no es estable. Si algo entra o sale del conjunto mientras recorres, una fila puede aparecer dos veces o ninguna. Para un barrido exacto sobre datos que se mueven, usa los endpoints con cursor —runs, contactos, ítems de una colección— y su iterate().

Las dos formas de paginar, con cuál te toca en cada dominio, están en Errores, idempotencia y paginación.

v2.15.0

WhatsApp interactivo, difusiones, carpetas y una guía nueva de operación

WhatsApp más allá del texto, en CRM y contactos. Botones (hasta 3), listas, respuestas citadas, reacciones, ubicación y ficha de contacto. El id de cada botón es tuyo y es lo que te vuelve cuando la persona elige, así que ponle algo que puedas interpretar sin una tabla aparte.

Lo que conviene saber antes de escribir código: fuera de la ventana de 24 horas desde el último mensaje de la persona, WhatsApp solo permite escribir con plantillas aprobadas por Meta. Es un límite de Meta y no se puede saltear.

Difusiones, en la misma guía. Dos cosas que cambian cómo las usas: previewCount resuelve el segmento y te dice a cuánta gente le va a llegar sin enviar nada, y una difusión no manda un mensaje suelto sino que arranca un flujo por cada contacto del segmento — por eso puede responder, ramificar y encadenar pasos.

Carpetas y slugs, en Datos: colecciones e ítems. El slug de un ítem es único dentro de su colección, no de todo el workspace. Y una distinción que ahorra dolores de cabeza: el código lo genera Meteor y no cambia; el slug lo pones tú y puede cambiar, así que no lo uses como identificador estable en tu base.

Guía nueva: Operación del workspace — consumo de Energía, archivos, conversaciones, recordatorios, autopiloto y grupos de agentes. Son cinco dominios que se consultan juntos cuando ya estás en producción. Lo que trae:

  • Cuando el consumo no cuadra, la respuesta está en billing.executions(), no en el agregado. Y si tu integración es desatendida, revisa autoRecharge(): quedarse sin Energía detiene las tareas.
  • Un recordatorio hace dos cosas distintas. Con channel_id y template_name se envía al contacto a la hora agendada; sin ellos es una nota interna para el equipo.
  • Para intervenir una conversación puntualmente, pauseAutopilot — no setAutopilot(false). Apagarlo es permanente hasta que alguien lo prenda; pausado con minutos se reanuda solo.
  • Los grupos de agentes son de personas, no de Mets. addMember recibe un user_id. Para acotar un Met, lo que se usa son sus habilidades y herramientas.
  • assets.cleanupProvisional() corre en simulación por defecto. Mira el resultado antes de pasar true; no es reversible.

Con esto el portal va en 20 guías, y la primera es Cómo construir un agente de IA.

v2.14.0

El CLI ya opera datos y CRM, y sabe decirte por qué tu key no funciona

met ya opera las colecciones, los ítems y los contactos desde la terminal, además de runs, tareas y eventos.

met collections show 12        # el esquema de campos
met items create --collection 12 --set titulo="Casa en el norte" --set precio=250000
met contacts send 42 "Ya quedó agendada la visita"

--set es repetible y se escribe sin escapar nada. Se parte en el primer =, así que una URL con query pasa entera.

Los tipos son la parte fina. Del shell todo llega como texto, y mandar "true" donde el campo espera un booleano lo guarda como texto sin que nada se queje. Así que --set convierte solo lo que no tiene ambigüedad: true, false, null y los números pasan; 007, 1.50, 1e3 y 0x10 quedan como texto, porque no sobreviven el viaje de ida y vuelta. Lo que no puede distinguir es un teléfono 3001234567 de una cantidad — para esos, el tipo lo pones tú:

met items create --collection 12 --data '{"telefono":"3001234567"}'

met contacts send manda como operador humano, no como Met: es el equivalente de escribir desde la bandeja, y queda atribuido a una persona en el historial. Si quieres que responda un Met, eso es met run.

met whoami responde *"¿por qué mi key no funciona?"* distinguiendo los tres casos que desde afuera se ven idénticos:

  • 401 — la key está mal, vencida o revocada.
  • 403 — la key es válida y le falta el scope de la operación. Es el que más tiempo hace perder: autentica sin problema y aun así falla, y la primera sospecha siempre cae en la key.
  • 2xx — key, workspace y host correctos.

También imprime contra qué host y qué workspace estás pegando, que es el otro error silencioso. Reemplaza a met keys: las API keys se crean y revocan desde el panel.

Está en @meteor.ia/cli 0.3.0. La guía completa: CLI.

v2.13.0

El servidor MCP ya escribe, y sabe qué hay en tu workspace

El catálogo MCP pasó de 16 tools en 6 dominios a 22 en 9. Hasta ahora un agente conectado a Meteor podía leer bastante y escribir muy poco: creaba contactos y tareas, instalaba plantillas, y ahí terminaba.

Cerrar el ciclo de una tarea. met_set_task_status era el eslabón que faltaba: se podían crear tareas y ejecutarlas, pero no marcarlas como terminadas. Un asistente que abre trabajo y nunca lo cierra deja el tablero peor de como lo encontró.

Escribir datos. met_create_item y met_update_item sobre las colecciones no-code. data lleva las claves del esquema de la colección — las ves con met_list_collections — y en met_update_item es un merge parcial: lo que no mandas queda como estaba. El título del ítem es el primer campo del esquema, no una clave name aparte.

Contexto del workspace. met_list_variables y met_list_skills. Las variables son los tokens que se interpolan en prompts, plantillas y mensajes: un asistente que no las ve redacta a ciegas para ese workspace. Las habilidades responden "qué sabe hacer esto" antes de pedirle algo a un Met.

Hablarle a un contacto. met_send_message envía por el canal activo del contacto, como operador humano. No hay que elegir canal: lo resuelve el contacto. Y si no tiene un canal conversacional activo, falla — antes que descartar el mensaje en silencio.

Cada tool declara el mismo scope que su endpoint REST, así que ninguna abre una puerta nueva: met_send_message pide channels:send, igual que POST /contacts/{id}/messages. Una key sin ese scope no ve la tool en el catálogo.

Los descriptores de /.well-known/mcp.json y /.well-known/server.json se regeneran del catálogo real, así que ya anuncian las 22.

v2.12.0

Python en las guías, y el CLI ya puede ejecutar tareas

El SDK de Python tiene paridad completa con el de TypeScript desde hace tiempo, y en el portal no había un solo ejemplo de Python. La mitad del SDK era invisible.

Pestañas TypeScript · Python · curl. En Autenticación, Ejecutar Mets, Contactos, Tareas y Errores. Funcionan sin JavaScript y se navegan con el teclado.

pip install meteor-ia

Ojo con una diferencia real: en Python las respuestas son diccionarios (run["output"], no run.output) y los métodos van en snake_case (send_message, create_note). Los ejemplos del portal salen de las firmas reales, no de traducir el de TypeScript.

El CLI ya ejecuta tareas. Antes cubría runs y eventos; ahora también tareas:

met tasks list
met tasks run tsk_123 --wait

--wait importa más de lo que parece. POST /tasks/:id/execute arranca la ejecución y devuelve enseguida, así que sin esperar el comando sale con código 0 por haber podido disparar, no por haber funcionado — en un cron, eso es un fallo que nunca te enteras. Con --wait el código de salida refleja el estado final.

Y si eres partner, el CLI ya te sirve. Una key de partner no tiene workspace, así que no tenía ningún camino en la terminal:

met partner leads --status won
met partner commissions --json

Son de solo lectura a propósito. Instala o actualiza con npm i -g @meteor.ia/cli y mira la guía del CLI.

El servidor MCP también creció: ahora son 16 tools, con met_execute_task y met_get_task_execution — antes un agente podía crear tareas y no ejecutar ninguna. Y el servidor es descubrible: https://developers.meteor.com.co/.well-known/mcp.json trae el endpoint, cómo autenticar y la lista de tools, generada del catálogo real.

v2.11.0

Los parámetros y los cuerpos del contrato dejan de estar mudos

El spec describía 383 parámetros sin una sola descripción. La referencia mostraba taskId y nada más, y saber si un {id} es un UUID o un entero era algo que se averiguaba llamando a producción a ver qué contestaba. Eso se cierra, y también mejora bastante lo que se documenta de los cuerpos que envías.

Los 383 parámetros están descritos. Los de ruta y los de query, en las dos superficies (41 más en la Partners API). No se escribieron uno por uno: esos 383 son 64 nombres distintos, así que el contrato los toma de un diccionario compartido y salen iguales en todas las operaciones. Un parámetro nuevo se describe una vez y aparece descrito en todos lados; si alguien agrega uno que no está en el diccionario, el generador lo reporta por nombre en vez de publicarlo mudo.

Cuando un endpoint quiere decir algo más específico, su propia descripción gana: el diccionario es el piso, no el techo.

Los cuerpos de las peticiones dicen mucho más. El contrato ahora se genera introspeccionando los tipos y los comentarios del código, no solo los nombres de los campos:

  • required pasó de 50 a 167 schemas. Antes, en la mayoría de los cuerpos no había forma de saber qué campo era obligatorio salvo mandarlo incompleto y leer el 400.
  • Las propiedades documentadas pasaron de 361 a 863, con 221 descripciones, más límites de longitud y ejemplos donde el código los declaraba.

Y una cosa que decidimos NO publicar. Al generar desde los tipos, 158 operaciones quedaron con un schema de respuesta que decía, literalmente, "esto devuelve un objeto". En la referencia eso se ve *igual que documentado* y no informa nada: quien lo lea no sabe ni un campo más que antes, pero cree que sí. Se podan; la respuesta 2xx sigue declarada, sin un cuerpo que finja.

Documentar de verdad lo que devuelve cada endpoint es el trabajo que sigue, y va de a uno —priorizado por el tráfico real de cada endpoint, no por intuición—. Preferimos que la referencia diga "todavía no está" antes que insinuar que sí.

v2.10.1

owner_kind admite METEOR, y se limpian dos schemas del contrato

Lo único que puede afectarte: owner_kind tiene un valor más.

En la Partners API, las tareas y los ítems de checklist de un proyecto declaran de quién es la responsabilidad del trabajo. Hasta hoy eran dos valores, PARTNER y CLIENT; ahora son tres:

owner_kind: "PARTNER" | "CLIENT" | "METEOR"

METEOR significa que el trabajo lo hace el equipo de Meteor. La situación existía y no existía la forma de decirla, así que se registraba como si fuera del partner.

Es un cambio aditivo: ninguna respuesta cambió de forma y los dos valores de siempre siguen significando lo mismo. Pero si tu código compara owner_kind contra una lista cerrada —un enum, un match exhaustivo, un switch sin rama por defecto— un valor nuevo puede hacerlo fallar. Conviene revisarlo antes de que te llegue el primer proyecto con tareas de Meteor. Si usas el SDK, actualízalo y el tipo ya lo trae.

Los enum cerrados son justamente la razón por la que esto se anuncia aunque del lado del servidor nada se haya roto: agregar un valor es compatible para quien lo emite y no siempre para quien lo lee.

Y dos schemas que salen de components, sin efecto para nadie:

  • SowItemDto — era el cuerpo de un endpoint interno de alcance de proyecto, que dejó de existir cuando el alcance y los hitos se unificaron en una sola lista. Nunca formó parte de una operación pública.
  • WhatsappWabaTargetDto — estaba declarado sin una sola propiedad y sin ninguna operación que lo usara. Era un artefacto de generación, no un contrato.

El conteo de operaciones —333 entre las dos superficies— no se movió.

v2.10.0

Los errores quedan documentados en el contrato, y el portal se puede usar

Hasta hoy el spec público describía 297 operaciones sin decir qué pasa cuando alguna falla: ni una sola respuesta de error documentada. Un dev tenía dos salidas, y las dos malas: llamar a ciegas contra producción, o leer los tipos del SDK en GitHub. Eso se cierra.

El catálogo de errores está en el contrato. Cada operación pública declara ahora sus respuestas 400, 401, 429 y 500, con el schema del cuerpo y un ejemplo real. La taxonomía es la de siempre —cinco type en lista cerrada, code en inglés, message en español— pero ahora la referencia interactiva la muestra en cada endpoint, en vez de esconderla en una guía.

  • error.typeauthentication_error, invalid_request_error, rate_limit_error, agent_error, api_error. El SDK los mapea 1:1 a clases de excepción.
  • error.code — específico y creciente: invalid_api_key, missing_scope, quota_exceeded, test_mode_restricted
  • request_idguárdalo. Es buscable en tu Workbench y es lo primero que pide soporte.

Vale igual para la Partners API: el catálogo vive en un solo lugar, así que las dos superficies no pueden divergir.

La referencia ya puede ejecutar requests. Con el botón *Test Request* pegas tu key y pruebas cualquier endpoint sin salir del portal. Usa una key met_test_ mientras exploras.

Buscador. ⌘K (o /) en cualquier página del portal: busca en las guías, en sus secciones, en las 333 operaciones de los dos specs y en el changelog.

Dos guías nuevas:

  • Skills — 20 operaciones que no tenían ni una línea de narrativa. Activar del catálogo, guardar credenciales, probar antes de vincular, y por qué activar una skill no se la da a todos tus Mets.
  • CLI (met) — el CLI existía, estaba publicado en npm y no tenía página. Incluye met listen --forward, que reenvía los eventos de tu workspace a tu servidor local para desarrollar webhooks sin túneles.

El servidor MCP deja de prometer lo que no tiene. La tabla de capacidades de MCP se genera del catálogo real: son 14 tools sobre 6 dominios. La tabla anterior estaba escrita a mano y listaba ocho dominios, cuatro de ellos sin una sola tool — y como el catálogo se filtra por los scopes de tu key, una tool que no existe se veía igual que un permiso faltante.

Y varias cosas que estaban rotas a la vista: las guías mostraban el título dos veces, el menú lateral no tenía índice de secciones, en el teléfono había que scrollear toda la navegación antes de llegar al texto, y compartir un link del portal no generaba preview. El changelog ahora tiene feed Atom.

v1.5.0

Exporta e importa flujos como JSON ("Flows as Code")

Un flujo de automatización ahora es un artefacto de texto portable: expórtalo a JSON, versiónalo en git, compártelo entre workspaces o genéralo por código. La definición del canvas viaja con sus referencias cruzadas (Mets, skills, MCPs, colecciones, canales, sub-flujos) reescritas como marcadores estables y descritas en un bloque dependencies — nunca datos vivos ni credenciales.

# Exporta un flujo
met api GET /workspaces/$WS/flows/$FLOW_ID/export > bienvenida.flow.json

# Previsualiza el import en otro workspace (no crea nada)
met api POST /workspaces/$WS2/flows/import/preview -d @<(jq '{doc: .}' bienvenida.flow.json)
  • GET /workspaces/:workspaceId/flows/:id/export (automations:read) — devuelve el documento portable { schema_version, kind, flow, dependencies, content_hash }. Se exporta lo publicado (o el borrador si nunca se publicó). Las referencias a datos vivos (contactos, ejecuciones) se anulan con aviso; se detectan posibles secretos pegados en texto libre.
  • POST /workspaces/:workspaceId/flows/import/preview (automations:read) — dry-run que no muta: valida el JSON, chequea tipos de nodo y resuelve cada dependencia contra el workspace destino (auto por nombre / código de sistema), reportando lo que quedó sin resolver.
  • POST /workspaces/:workspaceId/flows/import (flows:write) — crea el flujo como borrador desactivado. bindings permite mapear a mano las dependencias que no matchearon (o omitirlas); las que quedan sin resolver dejan la referencia vacía con aviso, sin romper la importación.
  • Importación idempotenteimport acepta target_flow_id: en vez de crear un duplicado, reescribe el borrador de ese flujo. Habilita el ciclo git-native (exportar → editar el JSON → reimportar sobre el mismo flujo). El preview informa existing_flow_id cuando ya hay un flujo con el mismo identificador, para elegir entre crear o actualizar.
  • Round-trip en el editor — "Copiar JSON" copia el subgrafo seleccionado (o todo el lienzo) al portapapeles; "Pegar JSON" lo fusiona en otro flujo del mismo workspace regenerando ids. Para cruzar workspaces se usa Exportar/Importar (que traduce las referencias a marcadores).

El formato reutiliza el motor de referencias de los Snapshots, así que un flujo exportado es honesto sobre lo que necesita para correr en otro lado.

v2.9.2

Un enlace nuevo para un adjunto, cuando lo necesites

Ahora que los adjuntos del hilo son privados, su enlace caduca a los 7 días. Eso está bien para leer el hilo —cada lectura trae enlaces recién emitidos— pero deja colgada a una integración que guarde referencias a documentos en su propio sistema: al octavo día, el enlace guardado no abre nada.

  • GET /partner/leads/:id/attachments/signed-url?storage_path=…
  • GET /partner/projects/:id/attachments/signed-url?storage_path=…
  • En el SDK: met.partner.leads.comments.signedUrl(leadId, storagePath) y su gemelo en proyectos, en TypeScript y en Python.

Devuelve el adjunto con un enlace recién firmado, más expires_in en segundos para que sepas cuánto te dura sin adivinarlo.

La regla, en una línea: guarda storage_path, no la URL. El storage_path no caduca nunca; la URL es de usar y tirar. Si ya guardaste URLs, no hace falta que migres nada — vuelve a pedirlas con el storage_path que viene en el mismo objeto.

Sin scope nuevo: monta el de lectura del objeto al que pertenece el hilo (partner:leads:read o partner:projects:read). Y el adjunto tiene que estar en ese hilo — un storage_path que no le pertenece devuelve 404, igual que uno que no existe.

v2.9.1

Los adjuntos del hilo dejan de ser públicos

Ayer, al anunciar el hilo de la relación, escribimos que los adjuntos quedaban en un bucket público y que no subieras por ahí nada que no pudieras compartir por link. Ya no es así: los archivos del hilo viven en un bucket privado y solo se entregan por URL firmada. Un GET sin credencial ya no abre nada.

Qué cambia para ti:

  • POST …/comments/upload ahora devuelve también bucket: es dónde quedó el objeto, y el servidor lo necesita para volver a firmar la URL cada vez que lees el hilo. Mándalo de vuelta en attachments, junto con el resto de la metadata, tal como lo recibiste.
  • La url que te devuelve el upload caduca. No la guardes como si fuera permanente: cada lectura del hilo trae una URL fresca. Lo que sí es estable es storage_path.
  • El campo type (siempre "file") ahora se acepta explícitamente. Si reenviabas la respuesta del upload completa, ya no te la rechaza.

Si hasta hoy armabas el objeto a mano con solo url y storage_path, sigue funcionando: bucket y type son opcionales, y los adjuntos que subiste antes se siguen leyendo.

v2.9.1

Paridad completa en los SDKs, y Tareas estrena guía

Auditamos las 297 operaciones públicas contra lo que realmente ofrecen los SDKs. Dos huecos, los dos cerrados:

  • met.variables.retrieve(id) — la única asimetría CRUD que quedaba: se podía listar, crear, actualizar y borrar una variable, pero no leer una sola por su id.
  • met.snapshots.submit(id) — enviar un snapshot propio a revisión editorial. El scope snapshots:publish existía en el contrato y no tenía superficie en ningún SDK: aparecía en la lista de scopes de tu key y no había forma de usarlo.

Con esto, TypeScript y Python cubren 297 de 297 operaciones, y ninguna ruta de los SDKs apunta a algo que no exista en el spec.

También estrena guía Tareas — 35 operaciones que hasta hoy solo existían en la referencia generada. Cubre el modelo (la tarea es la receta, la ejecución es el plato), los pasos, los disparadores, comentarios con adjuntos, vistas guardadas, y sobre todo las pausas para una persona: la diferencia entre aprobar un paso que el Met ya hizo y completar un paso que hiciste tú, y por qué las tres operaciones van contra el id de la ejecución y no el de la tarea.

Sabemos lo que falta y no lo escondemos: Skills (20 operaciones) sigue sin guía, y el CLI met cubre hoy run, runs, chat y listen — los dominios grandes (tareas, colecciones, contactos) todavía no tienen comando.

v2.9.0

Partners API — el hilo de la relación, con adjuntos

La conversación sobre una oportunidad y la de su proyecto son la misma. No es una decisión de la API: para Meteor, oportunidad, cliente y proyecto no son tres cosas sino la misma relación en tres momentos, y ahora la superficie pública lo refleja. Lo que escribiste mientras vendías sigue ahí cuando estás implementando — sin copiar nada, porque nada estaba en el lugar equivocado.

  • GET /partner/leads/:id/comments · GET /partner/projects/:id/comments — el hilo, del mensaje más viejo al más nuevo.
  • POST /partner/leads/:id/comments · POST /partner/projects/:id/comments — escribe un mensaje, con o sin adjuntos.
  • POST …/comments/upload — sube un archivo (multipart file) y devuelve su metadata para mandarla en attachments.
  • DELETE /partner/lead-comments/:id · DELETE /partner/project-comments/:id — borra un mensaje propio.
  • En el SDK: met.partner.leads.comments.* y met.partner.projects.comments.* (list · create · upload · delete), en TypeScript y en Python.

No hay scope nuevo. El hilo monta el de su dominio: leerlo pide partner:leads:read o partner:projects:read, escribirlo el :write correspondiente. Quien puede ver la oportunidad ve su conversación, y no antes — un scope aparte de "comentarios" habría dejado leer conversaciones de objetos que la key no puede ver.

Tres límites, dichos sin eufemismos:

  • Los adjuntos quedan en un bucket público: cualquiera con la URL abre el archivo, sin autenticación. No subas por ahí nada que no puedas compartir por link.
  • El tope por archivo es 25 MB (la superficie interna acepta más, pero ahí hay una sesión de por medio).
  • Las menciones no se pueden mandar por API: dependen de ids internos del equipo, y un id equivocado le notificaría a la persona equivocada. Salen en la lectura, como nombres.

También en esta versión: el SDK de Python alcanzó la paridad completa con el de TypeScript en met.partner — antes cubría seis rutas de solo lectura, ahora las 34 operaciones, incluidas escritura de oportunidades y proyectos, tareas, hitos, tickets y el hilo.

Todavía no: el hilo del cliente —el que no cuelga de una oportunidad ni de un proyecto— y los comandos de partner en el CLI met, que hoy opera sobre un workspace.

v2.8.0

Partners API — tickets de soporte

Soporte estrena superficie pública, y arranca por la API de partners: ya puedes abrir, responder y cerrar tickets desde tu propio portal.

  • GET /partner/tickets · GET /partner/tickets/:id (con el hilo de mensajes) · GET /partner/tickets/reasons, con el scope partner:support:read.
  • POST /partner/tickets · POST /partner/tickets/:id/messages · PATCH /partner/tickets/:id/status, con el scope partner:support:write.
  • En el SDK: met.partner.support.list/get/reasons/create/reply/updateStatus.

Con workspace_id el ticket se abre a nombre de un cliente de tu cartera y ese cliente lo ve en su Met; sin él es un ticket tuyo hacia Meteor. Es la pieza que te deja poner una mesa de ayuda de primer nivel en tu portal sin sacar al cliente de tu experiencia.

Estados: new · in_progress · waiting_client · resolved · closed.

Dos límites que la API respeta igual que el portal: las notas internas del equipo de Meteor nunca salen por esta superficie, y los tickets de prioridad crítica los gestiona solo Meteor.

Próximo: adjuntos y comentarios en oportunidades, proyectos y tickets.

v2.7.0

Partners API — escritura de oportunidades y proyectos

Si operas tu propio portal, ya puedes registrar y mover oportunidades y proyectos en Meteor desde ahí. Lo que creas por API es tuyo desde el primer momento: una oportunidad nace aceptada y a tu nombre, sin pasar por el pool de Meteor.

  • OportunidadesPOST /partner/leads, PATCH /partner/leads/:id, POST /partner/leads/:id/move, DELETE /partner/leads/:id y el detalle GET /partner/leads/:id, con el scope partner:leads:write. En el SDK: met.partner.leads.create/update/move/delete.
  • ProyectosPOST /partner/projects, PATCH /partner/projects/:id, POST /partner/projects/:id/advance-phase, tareas (/tasks, /project-tasks/:id), hitos y el detalle GET /partner/projects/:id, con el scope partner:projects:write. En el SDK: met.partner.projects.* y el sub-recurso met.partner.projects.tasks.
  • Idempotencia — los POST aceptan Idempotency-Key y el SDK genera uno por request: un reintento por timeout no te deja dos oportunidades iguales.
  • Etapas del pipeline: NUEVO · CONTACTADO · CALIFICADO · PROPUESTA · GANADO · PERDIDO.

Tres acciones se siguen gestionando solo desde el portal de Meteor, porque mueven dinero o atribución: proponer y aprobar el precio, asignar implementador, y convertir una oportunidad en cliente. advancePhase() las respeta — falla si el precio no está aprobado, si quedan hitos pendientes, o si el implementador no aceptó su oferta. Un proyecto creado por API es siempre de cliente.

Los dos scopes son exclusivos de keys de partner: una key de workspace que los pida recibe 403. Ya puedes marcarlos al crear una key desde Ajustes → Desarrolladores en tu portal.

Próximo: tickets de soporte (partner:support:*) y adjuntos y comentarios en oportunidades y proyectos.

v2.6.0

Imágenes, variables, visibilidad de colecciones y las tools de cada Met (SDK 0.5.0)

SDK @meteor.ia/sdk@0.5.0. Esta versión convierte en API de primera clase varias cosas que antes eran rodeos.

Generación de imágenes (met.images, scope integrations:execute, key live). Genera mockups sin resolver ids de integración a mano. Debita energía a tu workspace y devuelve una URL alojada por Meteor.

const img = await met.images.generate({ prompt: 'rótulo azul en fachada', aspect_ratio: '16:9' });
// img → { url, format, size_bytes, model, energy_debited }
await met.images.edit({ prompt: 'ponlo en acrílico', image_base64 });
await met.images.video({ prompt: 'giro 360', image_base64 });
  • POST /workspaces/{ws}/images/generate · /edit · /video.

Variables del workspace por API (met.variables.set/create/update/delete, scope variables:write). Guarda el token que tu Función HTTP resuelve con auth_workspace_variable — antes solo se podía desde el panel.

await met.variables.set('mi_api_token', '<TOKEN>', { encrypted: true });

Visibilidad de colecciones por Met (collections.expose_to_agent). Oculta una colección al LLM sin borrar los datos; aplica en todos los caminos (API, chat, canal).

await met.collections.update(collectionId, { expose_to_agent: false });

Las tools de un Met (met.agents.tools(agentId), scope agents:read). Descubre qué herramientas ve un Met y cuáles están deshabilitadas — el diagnóstico para saber por qué una Función no aflora, y qué nombres pasar a agents.update({ disabled_tools }).

const tools = await met.agents.tools(agentId);
// [{ name, source: 'collection'|'skill'|'function'|'internal', description, disabled }]
  • GET /workspaces/{ws}/agents/{agentId}/tools.

Bind de Met por conversación. En un Run, met (nombre o id) ejecuta ESE Met directo con sus Funciones. Con conversation_id, el primer met recibido queda fijado como default de la conversación: los runs siguientes que omitan met corren el mismo Met. Un met explícito siempre gana.

Errores de tool más claros. Cuando una Función HTTP falla, la respuesta indica la causa concreta (variable de auth ausente, 401 del endpoint, timeout) en vez de un error genérico. Nunca se exponen tokens ni secretos.

Mejoras de documentación del SDK: el handler HTTP directo de una Función quedó documentado como camino de primera clase (no necesitas un flujo para llamar a tu endpoint), y items.search aclara que es búsqueda por texto (léxica).

v2.5.0

Ejecuta un Met específico por Run, imágenes de entrada, y control fino de tools

Varias mejoras para construir el loop agéntico con precisión, pensadas con el feedback de los primeros integradores.

met en un Run ahora ejecuta ESE Met (bind, no hint). Antes el campo met era informativo y todo pasaba por el orquestador —que no cargaba las Funciones IA del Met—. Ahora, si pasas met (id o nombre), el Run ejecuta ese Met directamente con sus Funciones y tools:

const fn = await met.functions.create({ name: 'calcular_cotizacion', http: { url: '.../api/cotizar' } });
await met.functions.link(agentId, fn.id);

// Antes: la función no aparecía (el orquestador no la cargaba).
// Ahora: corre el Met "kami" con calcular_cotizacion disponible.
for await (const ev of met.runs.stream({ met: 'kami', input: 'Cotiza 3 piezas de acero' })) {
  console.log(ev.type, ev.data);
}

Si omites met (o no resuelve a un Met del workspace), el orquestador auto-rutea como antes. El metering es correcto en ambos casos (live cobra Energía, test es gratis).

Imágenes de entrada (visión). runs.create/stream acepta attachments para que el Met "vea" una imagen —clasificar una foto, leer un documento, validar un diseño—:

await met.runs.create({
  met: 'kami',
  input: '¿Este acabado coincide con el aprobado?',
  attachments: [{ url: 'https://.../foto-instalada.jpg', mime_type: 'image/jpeg' }],
});

Apaga tools por Met. met.agents.update({ disabled_tools }) desactiva tools nativas para un Met —por ejemplo, quitarle el acceso a las colecciones sin archivarlas—. Las tools deshabilitadas se ocultan del catálogo del modelo (ya no las ve ni las intenta) además de rechazarse en runtime:

await met.agents.update(agentId, {
  disabled_tools: [
    'mcp_meteor_list_collections', 'mcp_meteor_get_collection', 'mcp_meteor_list_items',
    'mcp_meteor_get_item', 'mcp_meteor_search_items', 'mcp_meteor_create_item',
    'mcp_meteor_update_item', 'mcp_meteor_set_item_name',
  ],
});

met.events — escucha el workspace en vivo. El SDK ahora expone el feed de eventos (met listen): una suscripción SSE efímera, sin montar un endpoint.

for await (const ev of met.events.stream({ types: ['contact.message.received', 'run.completed'] })) {
  console.log(ev.type, ev.data);
}

Más eventos que sí disparan. Se cablearon los emisores de contact.created, contact.updated, task.completed y conversation.handoff (antes estaban en el catálogo pero no llegaban). Suscríbete por webhook (met.webhooks.subscriptions) o escúchalos con met.events.stream.

v2.4.0

Construye el loop agéntico por SDK — Funciones que llaman tu endpoint, flujos y autopilot

El SDK 0.3.0 (npm i @meteor.ia/sdk@0.3.0) expone la superficie para armar todo el proceso agéntico desde tu backend, sin panel.

Funciones IA con handler HTTP directo. Una Función (una herramienta que tu Met puede llamar) ahora puede apuntar directo a tu endpoint HTTP — sin tener que armar un flujo:

const fn = await met.functions.create({
  name: 'lookup_price',
  description: 'Consulta un precio en el backend del cliente',
  parameters: [{ name: 'sku', description: 'Código de producto', required: true }],
  http: {
    url: 'https://tu-servidor.com/api/price',
    method: 'POST',
    sign_secret_variable: 'PRICE_SECRET', // firma cada request con X-Met-Signature
  },
});
await met.functions.link(agentId, fn.id);

Meteor llama tu endpoint con los argumentos que el Met recolectó (body JSON), lo firma con HMAC (X-Met-Signature) para que verifiques el origen con met.tools.createHandler, y devuelve tu respuesta al Met como resultado de la herramienta. Los secretos nunca se guardan en crudo: se referencian por nombre de variable del workspace (sign_secret_variable / auth_workspace_variable). También sigue disponible el handler por flujo (flow_id) con un nodo http.request, ahora creable por SDK con met.flows.*.

Nuevo en el SDK:

  • met.functions.* — crear/listar/actualizar/borrar Funciones IA + link/unlink a un Met (scopes functions:read/functions:write).
  • met.flows.* — crear un flujo, editar su lienzo (draft_definition), publicar, duplicar, archivar, ejecutar (flows:write / automations:*).
  • met.contacts.setAutopilot(id, enabled?) y pauseAutopilot(id, minutes) — prende/apaga o pausa el autopilot de un contacto para el handoff a un humano (scope handoff:manage).

Con esto el patrón completo es: crear el Met (met.agents.create), definir la Función que llama a tu API, vincularla, y —en canales— el Met responde solo cuando entra un mensaje.

v2.3.0

Registra tu propio servidor MCP HTTP por API (self-service)

Nuevo endpoint público: POST /workspaces/{workspaceId}/integrations (scope integrations:manage). Conecta tu propio servidor MCP HTTP a un workspace directamente por API/SDK — antes el alta de un MCP era solo interna (admin).

Al registrar, Meteor le hace tools/list a tu endpoint para validar que responde y cachear sus tools; queda activo de una vez. Los secretos van en credentials (write-only) y se rellenan en la plantilla auth_header — nunca quedan en el catálogo.

En el SDK: met.integrations.register(...).

const integ = await met.integrations.register({
  name: 'mi-conector',
  endpoint: 'https://tu-servidor.com/mcp',
  auth_header: 'Authorization: Bearer {token}',
  credentials: { token: process.env.MCP_TOKEN },
});
// integ.id → úsalo como `key` en integrations.tools / execute / deactivate

El endpoint se llama con safeFetch (guard anti-SSRF: se rechazan hosts loopback, IPs privadas y metadata). Las tools quedan disponibles para tus Mets y para met.integrations.execute(id, tool, input).

¿Cuándo usar esto vs una Función HTTP? Si solo quieres exponer un endpoint como herramienta, una Función con handler http (met.functions.create({ http: {...} })) es más simple. Registra un MCP cuando ya corres un servidor MCP con varias tools y quieres exponerlas todas de una.

v2.2.0

Gestiona tus webhooks salientes por API (registra tu endpoint sin el panel)

Nuevos endpoints públicos bajo /webhook-endpoints (scope webhooks:manage). Ahora registras el endpoint que recibe los eventos salientes de Meteor directamente por API o SDK — antes solo se podía desde el panel de Desarrolladores.

  • POST /webhook-endpoints — registra tu URL y los eventos que quieres escuchar (enabled_events, o ["*"] para todos). Devuelve el secreto de firma una sola vez: guárdalo para verificar cada entrega con met.webhooks.constructEvent(...).
  • GET /webhook-endpoints y GET /webhook-endpoints/event-types — lista tus endpoints y el catálogo de eventos disponibles.
  • DELETE /webhook-endpoints/{id} — elimina un endpoint.
  • POST /webhook-endpoints/{id}/test — envía una entrega de prueba firmada.
  • GET /webhook-endpoints/{id}/deliveries y POST /webhook-endpoints/{id}/deliveries/{deliveryId}/retry — inspecciona el log de intentos y reintenta una entrega puntual.

En el SDK: met.webhooks.subscriptions.*.

const sub = await met.webhooks.subscriptions.create({
  url: 'https://tu-servidor.com/hooks/met',
  enabled_events: ['contact.message.received', 'channel.run.completed'],
});
// sub.secret llega una sola vez — guárdalo.

Eventos nuevos en el catálogo:

  • contact.message.received (scope conversations:read) — entró un mensaje de un contacto por un canal (WhatsApp, etc.). Es el evento para construir productos conversacionales encima de Meteor sin hacer polling.
  • channel.run.completed / channel.run.failed (scope runs:read) — el Met respondió (o falló al responder) un mensaje de canal. Son distintos de run.completed/run.failed, que son Runs de la API con id consultable; un run de canal no tiene fila en Runs.

Cada entrega llega firmada con X-Met-Signature (HMAC-SHA256, con anti-replay). Verifícala con met.webhooks.constructEvent(rawBody, signature, secret) antes de procesar: si la firma no valida, responde 400 y no proceses.

v2.1.0

Import masivo de items entra a la API pública (upsert + insert-only)

Nuevo endpoint público: POST /collections/{collectionId}/items/import (scope items:write). Carga o actualiza muchos items de una colección en una sola llamada (hasta 5.000 filas por request), keyed por el nombre de cada field.

Tiene dos modos según mandes o no keyField:

  • Upsert (con keyField): hace match por data->>keyField. Si existe un item con ese valor lo actualiza (rellena/pisa con los valores no vacíos del archivo); si no, lo crea. Deduplica por clave dentro del mismo request. Es el modo para catálogos con clave natural — SKU, email, código de producto.
  • Insert-only (sin keyField): inserta todas las filas sin deduplicar. Es para datos que no tienen una clave única por fila —por ejemplo líneas de venta que comparten la misma fecha—, donde el upsert las colapsaría en un solo registro. Es también el camino de la carga inicial cuando creás una colección desde un spreadsheet.

La respuesta trae el balance del lote: { created, updated, skipped, errors }, con errors[] detallando la fila y el motivo (sin clave, clave duplicada en el archivo, o fila sin columnas conocidas) para que puedas reintentar solo lo que falló.

Nota de compatibilidad. keyField es opcional. Si ya venías llamando este endpoint con keyField, tu integración sigue funcionando igual (modo upsert). Omitirlo es lo único que activa el nuevo modo insert-only.

Las columnas del archivo que no correspondan a un field de la colección se descartan (no ensucian items.data). El import no dispara automatizaciones ni eventos por fila: es una carga de datos, no una secuencia de acciones de usuario.

v2.0.0

RAG sale de la API pública hasta que esté terminado

Los 4 endpoints de /rag/* dejan de ser públicos. Una API key ya no los alcanza (403 endpoint_not_public) y desaparecen del spec, del SDK y de esta referencia:

  • POST /workspaces/{workspaceId}/rag/search
  • POST /workspaces/{workspaceId}/rag/search-collections
  • POST /workspaces/{workspaceId}/rag/reindex
  • GET /rag/status

Los scopes rag:read y rag:write salen del selector al emitir una key. Si tienes una key que ya los tiene, la key sigue siendo válida — esos scopes simplemente no habilitan nada.

Por qué. Estábamos ofreciendo una capacidad que no funciona de punta a punta. Auditamos antes de retirarla:

  • Nada indexa. Los items no se indexan al crearse ni al editarse. El índice solo se llena con un /rag/reindex manual, así que queda viejo apenas alguien crea un item. Para que te sirviera, tendrías que reindexar todo el tiempo.
  • Nada consulta. Ningún Met usa la búsqueda semántica para responder — usan la búsqueda léxica con scoring de items. RAG era una isla.

Preferimos retirarla a dejarla publicada como si anduviera.

Sobre nuestra propia política. Nuestra convención dice que un endpoint público nunca se elimina sin un ciclo de deprecación completo (headers Deprecation/Sunset y un período de convivencia). Acá lo saltamos, y queremos que sepas exactamente por qué en vez de que lo descubras solo:

Esos endpoints fueron públicos 7 días (del 10 al 17 de julio de 2026) y no por una decisión de producto: entraron barridos en una pasada masiva que decoró los 68 controllers de una. Sumado a que la feature no funcionaba completa, la probabilidad de que exista una integración real es prácticamente nula. La regla existe para no romperle el código a nadie; concluimos que no hay nadie a quien romperle.

Si nos equivocamos y esto te rompió algo, escríbenos — es un error nuestro y lo resolvemos contigo.

Qué haría falta para que vuelvan. Indexado incremental (que un item se indexe al crearse y al editarse) y que los Mets usen la búsqueda semántica. Con eso, volver a publicarlos es aditivo y no rompe a nadie. No hay fecha.

v1.8.0

La extracción con IA también completa tus campos personalizados

POST /contacts/{id}/extract ya no se limita a los campos de sistema ($name, $email, $city, $country): ahora también completa los campos personalizados de tu workspace, con las mismas reglas de siempre — fill-only, una sola llamada, un solo cobro.

Lo que necesitas saber si integras:

  • Solo campos de tipo text. El modelo devuelve texto, así que los select, currency, date y number quedan afuera por diseño. De los campos del pipeline sembrado, eso deja afuera a etapa, monto, fecha_cierre_estimada y lead_score. etapa además es el juicio del humano y la IA no lo toca nunca.
  • La descripción del campo es la pista que recibe el modelo. Si tu campo presupuesto_estimado tiene descripción *"cuánto dijo que puede gastar"*, eso es literalmente lo que se le pide extraer. Sin descripción se usa el label. Vale la pena escribir buenas descripciones: son el prompt.
  • Tope de 10 campos por llamada, con los de sistema primero. Cada campo alarga el prompt y el prompt se cobra; si tienes 40 campos personalizados, no se piden los 40.
  • lead_score_motivo no entra aunque sea text: lo escribe la IA en el mismo JSON, junto con el score.

Todo lo demás del endpoint sigue igual: filled mantiene su forma y significado (los campos que se completaron), el score se sigue devolviendo en lead_score, y con crm_extraction_enabled apagado no se llama al modelo ni se cobra.

Descubrí tus campos y sus tipos con met.variables.fieldDefinitions().

v1.7.0

Enriquecimiento web del contacto, con las fuentes a la vista

  • POST /contacts/{id}/enrich (contacts:write): busca al contacto en Google por su nombre —más el dominio de su correo corporativo, si lo tiene— lee las páginas que encuentra y completa solo los campos vacíos: empresa, cargo, $city, $country.

Devuelve filled con lo que completó y sources con las URLs de donde salió. Las mismas fuentes quedan en una nota del contacto.

Tres cosas que importan si integras:

  • Es bajo demanda, no automático. Sale a internet, cuesta bastante más que analizar una conversación y puede encontrar a un homónimo. Que lo dispare alguien que mira el resultado es parte del diseño.
  • Las fuentes no son decoración. El modelo tiene instrucción de omitir cualquier dato que no pueda confirmar que es de esta persona —un {} vacío es una respuesta correcta y preferible a una equivocada— pero los nombres se repiten. Por eso se citan: para que un humano pueda auditar de dónde salió cada dato.
  • Fill-only, y lo que dijo el contacto gana. Si él dijo que trabaja en X, gana X aunque una página diga otra cosa. La web nunca pisa la conversación.

Un contacto sin $name no se busca (un teléfono suelto no es una consulta). Los dominios de correo personales —gmail, hotmail, outlook…— no entran en la búsqueda: no dicen dónde trabaja alguien y solo traen homónimos.

Consume energía (crm_enrichment, una tarea ejecutada por uso). Requiere que el workspace tenga el enriquecimiento habilitado (crm_enrichment_enabled, apagado por defecto); apagado devuelve filled: [] sin buscar ni cobrar.

  • Campos sembrados: empresa y cargo (text) se suman a los del pipeline en contact_field_definitions, con la misma regla fill-only que el resto: si tu workspace ya tenía un campo con ese field_key, se respeta el tuyo. Descubrilos con met.variables.fieldDefinitions().

En la app aparece como el botón «Enriquecer desde la web» en el perfil del contacto, y un interruptor en Ajustes → Conversaciones que decide si ese botón existe — la palanca del admin para controlar cuánta energía se va en enriquecer a mano.

v1.6.0

Calificación de leads con IA, en la misma llamada que la extracción

  • POST /contacts/{id}/extract (contacts:write) ahora devuelve lead_score además de filled. Es un entero de 0 a 100: qué tan cerca de comprar está el contacto según lo que se dijo en la conversación. El motivo queda en el campo lead_score_motivo del contacto — un número sin justificación no es accionable.

No es un endpoint nuevo ni una ejecución nueva. Es la misma lectura del transcript y el mismo débito de energía que ya hacía la extracción: el modelo lee la conversación una vez y devuelve las dos cosas. Si venías llamando a este endpoint, ya estás recibiendo el score — filled sigue significando exactamente lo mismo y no cambió su forma.

Dos diferencias con los campos de ficha que importan si integras:

  • El score no es fill-only. Los campos ($name, $email, $city, $country) solo se llenan si están vacíos: el humano y el webhook mandan sobre el modelo. El score se recalcula en cada llamada, porque es un juicio sobre la conversación, no un hecho — si no se recalculara quedaría congelado en la foto del primer cierre.
  • La IA nunca toca etapa. Ese campo es el juicio del humano sobre el embudo y le pertenece. lead_score es el juicio de la IA. Cada uno manda en el suyo, y por eso son campos distintos.

lead_score viaja siempre en la respuesta, incluso como null (análisis apagado, sin saldo, o conversación sin mensajes): no hace falta distinguir "no hay score" de "la clave no vino".

  • Campos sembrados: lead_score (number) y lead_score_motivo (text) se suman a los del pipeline en contact_field_definitions. Como el resto del seed son fill-only: si tu workspace ya tenía un campo con ese field_key, se respeta el tuyo y no se toca. Descubrilos con met.variables.fieldDefinitions(); se ordenan y agrupan en el tablero como cualquier campo personalizado.

En la app, el interruptor de Ajustes → Conversaciones pasó de «Completar datos del contacto con IA» a «Analizar el contacto con IA»: es el mismo flag (crm_extraction_enabled), la misma llamada y el mismo cobro, ahora haciendo las dos cosas. No se agregó un interruptor aparte para la calificación porque no hay una decisión aparte que tomar.

v1.5.0

Detección de duplicados, fusión de contactos e import con mapeo de columnas

  • GET /contacts/{id}/duplicates (contacts:read): posibles duplicados por teléfono (tolerante al formato: con o sin código de país) o email (ignorando mayúsculas). Solo lectura. Cada candidato trae can_merge y, cuando es false, el blocked_reason.
  • POST /contacts/{id}/merge (interno por ahora — es destructivo y el contrato está en maduración): el contacto de la URL sobrevive, el de loser_id se fusiona y se borra. dry_run es true por defecto: la llamada te devuelve qué movería (mensajes, tareas, recordatorios, roles, envíos, items vinculados) sin tocar nada; hay que mandar dry_run: false explícito para que ocurra. Es atómico y fill-only: el superviviente conserva todos sus valores y del duplicado solo toma lo que le falte.

Una restricción que importa si integras: no se pueden fusionar dos contactos que tengan conversación propia. El webhook entrante identifica al contacto por $user_ns + $channel_id, y un contacto solo puede tener un $user_ns por canal — fusionarlos perdería una identidad y el próximo mensaje entrante desharía la fusión. El endpoint lo rechaza con el motivo en vez de hacerlo a medias.

  • Import con campos personalizados: POST /contacts/import acepta custom por fila, con el field_key pelado como clave ({ "custom": { "etapa": "Nuevo", "monto": "6300000" } }). Solo se aceptan claves que ya existan como field-definition del workspace — descubrilas con met.variables.fieldDefinitions(). Las demás se descartan en silencio: una clave inventada quedaría en data sin ninguna vista que la muestre.

En la app, el modal de importar ahora mapea las columnas: subes el archivo como lo tengas, Met adivina qué es cada columna por el encabezado (incluye sinónimos frecuentes de otros CRMs) y tú corriges lo que haga falta. Antes exigía que el archivo tuviera columnas llamadas exactamente name y phone.

v1.4.0

Línea de tiempo del contacto, tareas vinculadas y extracción de datos con IA

Tres cosas nuevas en el dominio de contactos, todas aditivas:

  • GET /contacts/{id}/timeline (contacts:read, met.contacts.timeline()): la historia del contacto en una sola lista — cambios de campo, eventos de sistema (estado, asignación), notas internas y tareas de seguimiento. Cada entrada trae un type discriminador (field_change, system_event, note, task, contact_created), así que no hay que inferir nada de la forma del dato. No incluye los mensajes de la conversación: para eso está messages(). Cursor: before = created_at de la última entrada recibida.
  • Tareas vinculadas al contacto: POST /workspaces/{ws}/tasks acepta contact_id, y GET /workspaces/{ws}/tasks?contact_id= devuelve las de un contacto. La respuesta sigue siendo un array plano — no cambiamos la forma de un endpoint ya publicado. Las tareas traen responsable, fecha límite, pasos y comentarios del módulo de Tareas: no hay entidad nueva.
  • POST /contacts/{id}/extract (contacts:write): completa con IA los campos vacíos del contacto con lo que se dijo en la conversación. Dos cosas importantes: consume energía (aparece en tu ledger como crm_extraction) y es fill-only — nunca pisa un dato existente. Está apagada por defecto: si el workspace no la habilitó en Ajustes, el endpoint devuelve filled: [] sin llamar al modelo ni cobrarte.

También, si construiste automatizaciones: se retiró del catálogo el disparador contact.status_changed. Estaba deshabilitado y ningún evento lo emitía nunca — no se podía usar, así que no hay nada que migrar. Para reaccionar a un cambio de estado o de etapa, usa contact.field_updated con el campo correspondiente ($status o etapa), que funciona hoy.

v1.3.0

Embudo comercial en contactos, totales agregados y catálogo de campos personalizados

El CRM estrena pipeline. La decisión de diseño importa para quien integra: no hay una entidad "oportunidad" nueva. El embudo son campos personalizados del contacto, así que se leen y escriben con las tools de contactos que ya usas, y todo lo que construiste encima sigue funcionando igual.

  • Tres campos sembrados en cada workspace: etapa (lista: Nuevo, Calificado, Propuesta, Negociación, Ganado, Perdido), monto (moneda) y fecha_cierre_estimada (fecha). Se siembran solo si no existían: si tu workspace ya tenía un campo con ese field_key, se respeta el tuyo — ni sus opciones ni su etiqueta se tocan. Mover una etapa es un PATCH /contacts/{id} normal, y dispara los mismos disparadores de contact.field_updated de siempre.
  • GET /contacts/pipeline-summary (scope contacts:read, met.contacts.pipelineSummary()): conteo y suma por etapa, agregados en la base sobre todos tus contactos, no sobre una página. Los contactos sin etapa se agrupan en __no_value__. Acepta stage_field/amount_field si tu workspace usa otros nombres.
  • met.variables (scopes variables:read/variables:write): los endpoints de campos personalizados ya eran públicos pero el SDK no los exponía. met.variables.fieldDefinitions() es la única forma de descubrir qué campos tiene un workspace y qué valores acepta cada lista — imprescindible para escribir en ellos sin adivinar.
  • Tipo de campo currency: nuevo tipo para las definiciones de campos de contacto. El código ISO de la moneda va en options (['COP']) y el valor del contacto es solo el número, para que sume limpio.
  • Documentada la convención de claves, que era invisible y es la causa más común de errores al escribir: los campos de sistema llevan $ ($name, $status); los personalizados, su field_key pelado (etapa, monto). Mandar $etapa no falla — guarda una clave que ninguna vista lee, y el dato queda invisible. Está en la guía de contactos.
v1.2.0

Paginado por cursor en contactos (arregla el barrido completo) y vistas de tareas en el contrato

Si barrías tu lista de contactos con contacts.iterate() del SDK, solo recibías la primera página y el barrido terminaba declarando éxito. Este release lo arregla y deja el recurso alineado con el paginado por cursor del resto de la API (§3.9):

  • GET /contacts devuelve has_more. El campo faltaba, así que el auto-paginado del SDK lo leía como undefined y cortaba en la página 1. total y page siguen intactos: si ya consumes la respuesta actual, nada cambia para ti.
  • Cursor estable con starting_after. GET /contacts?order=id&starting_after=<id del último contacto> pagina por cursor sobre un orden inmutable. El orden por defecto (recencia de la conversación) se reordena con cada mensaje entrante: un barrido sobre él se saltea contactos que saltan a la primera página a mitad del recorrido. Por eso el cursor exige order=id, y hay que pedirlo desde la primera página.
  • contacts.iterate() ya lo hace solo. El SDK pide order=id y encadena el cursor por ti; actualiza a la última versión y el barrido recorre todos tus contactos. Si paginabas a mano con page, sigue haciéndolo — pero para exports completos prefiere el cursor.

También, sin cambios de código: el contrato publicado estaba desactualizado y ya incluye las vistas de tareas (/workspaces/{ws}/task-views, /task-views/{viewId}, …/task-views/reorder, con scopes tasks:read / tasks:write). Están vivas desde el 15 de julio; lo que faltaba era su documentación en el spec.

v1.1.0

Autoría de flujos por API key, filtros de items, PATCH parcial y adjuntar por URL

Un paquete de mejoras nacido de integrar cotizadores y pipelines comerciales contra la API pública. Reduce fricción en automatización-como-código y en la gestión de datos no-code:

  • Autoría de flujos y automatizaciones con la key de servicio. Las mutaciones de flujos (POST/PATCH/DELETE/duplicar en /workspaces/{ws}/flows[/{id}]) y de disparadores (POST/PATCH/DELETE en /workspaces/{ws}/automations) ya no exigen el JWT de sesión: se emiten con los scopes nuevos flows:write y automations:write. Permite versionar la automatización como código y editarla desde CI.
  • Filtro y orden de items en el servidor. GET /collections/{id}/items acepta filter[campo]=valor (o filter[campo][op]=valor, con opeq, neq, gt, gte, lt, lte, like, ilike, in) y sort=-updated_at,campo. Ya no hace falta bajar la colección entera y filtrar en el cliente. Sobre claves de data, las comparaciones son de texto.
  • PATCH parcial de un item. PATCH /collections/{id}/items/{itemId} fusiona (merge) las claves recibidas en data sin reemplazar el objeto completo — evita las carreras del PUT. Complementa el PATCH …/field (un solo campo) existente.
  • Adjuntar un archivo por URL. POST /items/{itemId}/files acepta además un cuerpo JSON { url, filename }: Met descarga el archivo (con protección anti-SSRF) y lo guarda, sin streamear los bytes por multipart. La respuesta ahora incluye download_url (URL firmada de descarga directa).
  • dry_run en envíos de plantilla. POST /channels/{id}/whatsapp/send-template?dry_run=true valida plantilla y parámetros y devuelve el mensaje resuelto sin entregarlo — útil para verificar el payload en desarrollo.
  • Firma HMAC saliente en el nodo _Petición HTTP_. El nodo http.request puede firmar su cuerpo con un secreto del workspace y enviar la firma en X-Met-Signature (HMAC-SHA256), verificable en el destino — reemplaza el pasar una clave en la query.
  • Contrato más rico. Los DTOs de envíos de WhatsApp y de disparadores traen properties y examples; el POST con Idempotency-Key queda documentado en el contrato (reintento seguro sin duplicados).
v1.7.0

Marketplace de Snapshots — catálogo e instalación por API

Ya puedes descubrir e instalar plantillas de implementación (Snapshots) con tu API key. Un Snapshot empaqueta la configuración de un workspace —Mets, flujos, colecciones, habilidades e integraciones, sin secretos ni datos vivos— y se instala en otro workspace de forma aditiva e idempotente.

Nuevos endpoints públicos:

  • GET /workspaces/{workspaceId}/snapshot-catalog y GET /workspaces/{workspaceId}/snapshot-catalog/{slug} — navega el catálogo de plantillas publicadas y consulta la ficha de una (qué incluye, qué deberás conectar, vista previa de la guía). Requiere el scope snapshots:read.
  • POST /workspaces/{workspaceId}/snapshot-catalog/{snapshotId}/claim — reclama el acceso gratuito a una plantilla (crea el *entitlement*). Requiere snapshots:install.
  • POST /workspaces/{workspaceId}/snapshots/{snapshotId}/install — instala la última versión lista en el workspace. Corre como job asíncrono e idempotente por *provenance*: reinstalar no duplica. Requiere snapshots:install.
  • POST /workspaces/{workspaceId}/snapshots/{snapshotId}/submit — envía tu Snapshot a revisión editorial para publicarlo en el marketplace. Requiere snapshots:publish (solo partners).

La instalación es aditiva (nunca borra nada del workspace destino) y todo lo ejecutable llega desactivado hasta que lo enciendas. El pago de plantillas de pago y la distribución por *share link* siguen siendo superficie interna por ahora.

v1.6.0

Webhooks de apps OAuth + rotación de secreto con gracia

Dos mejoras para quienes construyen apps OAuth de terceros:

  • Eventos app.authorized y app.revoked — ahora te avisamos por webhook cuando un workspace autoriza o revoca tu app. El evento se entrega al workspace dueño de la app (el que la registró), no al que autoriza — igual que account.application.authorized de otros ecosistemas. Suscribite desde Desarrolladores → Actividad → Webhooks (requiere el scope integrations:read). El payload trae client_id, el workspace_id que autorizó, los scopes y el grant_id — sin datos personales del usuario.
  • Rotación de secreto con ventana de gracia (24h) — al rotar el client_secret de una app confidencial, el secreto anterior sigue siendo válido durante 24 horas. Así puedes desplegar el nuevo secreto sin una ventana de caída: los dos autentican mientras haces el cambio, y el viejo caduca solo.

Sin cambios en el contrato de la API pública: los access tokens OAuth siguen operando con los mismos scopes, cuota y Energía que una API key.

v1.5.0

OAuth 2.0 para apps de terceros

Ya puedes construir apps que otros workspaces de Meteor autorizan para operar en su nombre, sin pedirles nunca una API key. Es el estándar OAuth 2.0 Authorization Code + PKCE:

  1. Registras tu app en Ajustes → Desarrolladores y obtienes un client_id (y un client_secret si es confidencial).
  2. Rediriges al usuario a la pantalla de consentimiento de Meteor, donde ve tu app y los permisos exactos que pides.
  3. Al aprobar, recibes un código que canjeas en POST /oauth/token por un access token (Bearer, 1h) y un refresh token.
  • PKCE obligatorio (S256) — protege el flujo también para apps públicas (nativas/SPA) que no pueden guardar un secreto.
  • Scopes acotados — tu app declara sus scopes máximos y el usuario aprueba un subconjunto; jamás alcanzas más de lo que la app registró, y nunca scopes de partner.
  • Refresh con rotación — cada refresh emite uno nuevo e invalida el anterior; si un token robado se reusa, la sesión entera se revoca automáticamente.
  • Control del usuario — quien te autorizó ve tus apps en Ajustes → Desarrolladores y puede revocar el acceso cuando quiera (cae en cascada sobre tokens y refresh).

Los access tokens OAuth operan la misma API pública, con los mismos scopes, cuota y Energía que una API key — nada de superficie nueva del lado del recurso.

v1.4.0

Stream de eventos en vivo + met listen

Nuevo endpoint para escuchar los eventos de tu workspace en tiempo real, y el comando de CLI que cierra el dev-loop de webhooks:

# Reenvía cada evento a tu app local, firmado igual que en producción
met listen --forward http://localhost:3000/webhooks
  • GET /events/stream (events:read) — suscripción SSE efímera al flujo de eventos del workspace. No crea un webhook persistente: es para desarrollo e inspección. Cada evento llega como el mismo envelope público que entrega un webhook (event: message).
  • Filtrado seguro — el stream solo emite eventos cuyo scope de lectura porta tu key (una key events:read sin billing:read no ve billing.threshold), y respeta el modo: una key met_test_ solo ve eventos de test. Opcionalmente, ?types=run.completed,run.failed.
  • met listen --forward <url> — reenvía cada evento a tu servidor local con la misma firma HMAC (X-Met-Signature) que en producción, usando un secreto de firma efímero que el CLI imprime al arrancar. Así tu handler local corre exactamente la misma verificación (constructEvent) que usará en vivo — sin túneles ni configuración extra.

Para entrega garantizada y con reintentos en producción, sigue usando los webhooks persistentes; met listen es el compañero de desarrollo.

v1.3.0

SDK oficial de Python (meteor-ia)

Ya puedes construir sobre Meteor desde Python, con la misma ergonomía que el SDK de TypeScript:

from meteor_ia import Met

met = Met(os.environ["MET_API_KEY"], workspace_id=7)
run = met.runs.create("Resume los leads de hoy", met="ventas")

for event, data in met.runs.stream("Hola"):
    print(event, data)
  • Misma superficie curadaruns, agents, contacts, tasks, collections, items, billing, integrations, webhooks y partner.*. Mismos scopes, mismas rutas, mismo contrato que el SDK de TS.
  • Sin dependencias — usa solo la stdlib (urllib, hmac, json). pip install meteor-ia y listo.
  • Producción por defecto — reintenta 429/5xx con backoff (respeta Retry-After), Idempotency-Key automático en los POST, auto-paginación (.iterate()), streaming SSE (for event, data in met.runs.stream(...)) y errores tipados (MetRateLimitError.retry_after, MetAuthError, …).
  • Webhooks segurosconstruct_event(payload, signature, secret) verifica la firma HMAC saliente sin pegarle a la red.

Server-side only: tu API key met_* es secreta.

v1.2.0

Presupuestos de Energía por API key + webhook billing.threshold

Ahora puedes ponerle un tope de gasto de Energía mensual a cada API key y enterarte antes de que se agote:

  • Presupuesto por key — en *Ajustes → Desarrolladores* asignas un tope en USD por key (al crearla o después, sin rotarla). El consumo se mide del mismo ledger que cobra — no un contador aparte.
  • Aviso 50 / 80 / 100 % — te llega un email al dueño de la key en cada umbral, una sola vez por mes. Sin sorpresas a fin de mes.
  • Corte automático opcional (hard-stop) — si lo activas, al llegar al 100 % las nuevas solicitudes se rechazan con 429 budget_exceeded hasta el próximo mes o hasta que subas el tope. Si no, la key sigue operando y solo avisa.
  • Nuevo evento saliente billing.threshold — suscribe un webhook (scope billing:read) y recibe el cruce de cada umbral como evento: { api_key_id, level, period_month, budget_usd, spent_usd, currency }. Ideal para dashboards o para pausar tus jobs automáticamente.

Además, el consumo de Energía ahora se mide de forma completa: la generación de imágenes con GPT Image y la búsqueda semántica (RAG) debitan Energía con la misma política de precios que el resto de la plataforma (costo del proveedor + el margen de tu plan).

v1.1.0

Test mode, Developer Workbench, webhooks salientes y helpers del SDK

Esta versión suma las capacidades de las fases F2–F4 del plan:

  • Test mode (met_test_) — las keys de test corren el orquestador real sin costo (los runs de test se debitan a $0), con un cap diario por workspace. Los scopes con efecto externo (integrations:execute, channels:send, workspaces:provision) devuelven 403 test_mode_restricted.
  • Developer Workbench — Registros de requests (con request_id correlacionable vía X-Request-Id, sin bodies), Resumen de consumo (volumen, errores, latencia p95) y Salud (errores por code del catálogo + alertas de cuota). En Met y Partners.
  • Eventos salientes (webhooks firmados) — registras endpoints y Meteor te hace POST cuando pasa algo (run.completed, run.failed, contact.*, task.completed, conversation.handoff), con firma HMAC X-Met-Signature y reintentos con backoff.
  • Helpers del SDKmet.webhooks.constructEvent() verifica la firma de un evento saliente; met.tools.createHandler() hace lo mismo para endpoints HTTP que Meteor invoca como tools.
  • GitHub Secret Scanning — una key met_live_/met_test_ filtrada en un repo público se revoca automáticamente.
v1.1.0

Partners API — proyectos de implementación

met.partner.* suma su segundo dominio de crecimiento:

  • GET /partner/projects — proyectos de implementación donde el partner es originador o implementador, con el scope partner:projects:read. En el SDK: met.partner.projects.list().
  • Read-only y curado — fase, estado, precio (price_amount/price_status), título, descripción, deadline, workspace del cliente y nombres de originador/implementador. Omite los ids internos de partner y la mecánica del tablero.

Sigue en "Próximamente": partner:leads:write y workspaces:provision.

v1.1.0

Partners API — oportunidades (leads) del partner

La superficie met.partner.* suma su primer dominio de la fase de crecimiento:

  • GET /partner/leads — lista las oportunidades (leads) del partner de la key, con el scope partner:leads:read (exclusivo de keys de partner). En el SDK: met.partner.leads.list({ status }).
  • Read-only y curado — solo campos de negocio del lead (contacto, empresa, industria/zona, plan objetivo, estado, etiquetas). Nunca expone internals de asignación, atribución, notas del staff ni el cruce con clientes.
  • Filtra por status (NEW · ASSIGNED · ACCEPTED · REJECTED · CONVERTED); excluye las convertidas por defecto.

Sigue en camino del "Próximamente": partner:leads:write, partner:projects:read y workspaces:provision.

v1.1.0

Servidor MCP externo — la API de Meteor como tools para agentes

Cualquier agente que hable MCP (Claude Desktop, Cursor, Cline, tu propio stack) puede ahora conectarse a Meteor y usar tu workspace como tools:

  • EndpointPOST https://api.met.meteor.com.co/api/v1/mcp, transporte Streamable HTTP (@modelcontextprotocol/sdk), autenticado con tu misma API key (Authorization: Bearer met_...). El scope mcp:use abre la sesión.
  • Catálogo curado — no es una superficie paralela: cada tool mapea a la misma API pública y hereda su scope. La key solo ve las tools cuyos scopes tiene (y, si definiste tool_allowlist, las permitidas). Tools iniciales: met_run_agent (agents-as-tools — ejecuta los Mets del workspace), met_get_run, met_list_runs, met_list_contacts.
  • Agents-as-tools — un agente externo invocando a los Mets de tu workspace como una tool más. Agentes orquestando agentes.

Próximo: proyección int_* de tus integraciones activas (Shopify, Alegra, …) como tools, y OAuth 2.1 para los conectores de claude.ai/ChatGPT.

v1.0.9

Partners API — clientes, comisiones y payouts

*Entrada agregada el 2026-07-25: estos tres dominios se publicaron el 11 de julio junto con oportunidades y proyectos, pero se quedaron sin anuncio propio. Queda registrado para que el historial de met.partner.* esté completo.*

El namespace met.partner.* nace con su núcleo read-only, servido por el backend de partners y enrutado en el SDK a partnerBaseUrl:

  • GET /partner/clients — clientes atribuidos al partner: plan, estado, ciclo de facturación, comisión y consumo de Energía. Scope partner:clients:read. En el SDK: met.partner.clients.list({ page, limit, search }).
  • GET /partner/commissions — comisiones con monto, tasa aplicada, estado y trazabilidad de negocio. Scope partner:commissions:read. Filtra por status.
  • GET /partner/payouts y GET /partner/payouts/summary — liquidaciones (bruto, fee, neto, cadencia, referencia de pago) y el resumen de wallet. Scope partner:payouts:read.

Todo curado: nunca salen la mecánica de cálculo interna de una comisión ni las notas privadas del staff sobre un cliente.

Los cuatro scopes son exclusivos de keys de partner. Una key de workspace que los pida recibe 403, y el servidor lo re-verifica en cada request por owner_type.

Los dominios financieros se mantienen read-only por diseño: crear o editar comisiones y payouts pasa por reconciliación humana.

v1.0.0

Superficie pública inicial

Primera versión del contrato público de la Met API (F0 — contrato y anti-drift).

  • Se declara la superficie pública con @ApiPublic/@ApiInternal: 179 operaciones públicas sobre 444 rutas de meteorus.
  • Dominios públicos v1: collections, items (+content/comments/links/files), contacts, tasks (+steps/triggers/comments), rag, variables, files (workspace assets), skills, billing:read, automations:read.
  • openapi.public.json generado del código; el drift-check de CI garantiza que nunca quede desactualizado.

Aún no hay credenciales de API ni ejecución por key — eso llega en F1. Esta entrada documenta la congelación del contrato.

v1.0.0

Superficie conversacional en v1 (WhatsApp, handoff, flujos)

Lo conversacional es núcleo de Met, así que sube a la superficie pública v1 (antes marcado fase 2). Aditivo — no rompe nada de la superficie inicial. 179 → 215 operaciones públicas.

Nuevos scopes:

  • conversations:read — leer conversaciones WhatsApp/CRM que Met gestiona (7 ops: listar/leer conversaciones y mensajes, búsqueda, tool-calls).
  • handoff:manage — autopilot on/off/pause + marcar leído (3 ops).
  • channels:read — estado de canales y broadcasts, sin permiso de envío (3 ops).
  • channels:send — envío WhatsApp (buttons/list/location/contact/reaction/reply/template), mensajes a contacto y broadcasts (crear/cancelar/editar/borrar) — 13 ops.

Flujos suben bajo automations: listar/leer y ejecutar/publicar/testear (10 ops).

Curación (importante):

  • En Canales se exponen solo los métodos de envío. La configuración/conexión (tokens de WhatsApp, registro de webhook, phones, templates, config de canal) queda interna — nunca sale por SDK.
  • El chat crudo (/conversations/:id/messages/stream) sigue interno: la vía pública para *ejecutar/conversar con el Met* es la façade Runs (F1). Acá solo se exponen las lecturas de conversación.

Deudas de runtime (F1, antes de que esto sea alcanzable — hoy no hay auth por key):

  • channels:send exige gating anti-abuso (límites de Meta, anti-spam) antes de estar vivo.
  • Los payloads de conversación/mensaje deben curarse (no filtrar campos internos) al cablear los guards.
v1.0.0

Runs — ejecución de Mets por API (el plano agéntico)

Llega Runs: ejecutar el Met de tu workspace por API, con memoria opcional y streaming. Es el corazón de "infraestructura agéntica como servicio". Aditivo. 248 → 251 operaciones públicas.

Nuevos endpoints (scopes runs:execute / runs:read):

  • POST /workspaces/:id/runs — ejecuta el Met sobre input. Con conversation_id el run tiene memoria; sin él corre en una conversación efímera. stream: true → respuesta SSE con los eventos en vivo.
  • GET /runs/:id — estado + salida de un run.
  • GET /workspaces/:id/runs — historial paginado (cursor opaco, { data, has_more }).

Esquema público de eventos (estable, versionado): run.started, run.step, run.output, run.completed, run.failed. El run.step es una proyección curada del trace interno — expone tipo de paso, iteración, nombre de tool y actor; nunca prompts internos, inputs crudos de tools, costos de proveedor ni conteos de tokens.

Costo: cada run debita Energía por el mismo ledger de siempre, marcado execution_type='api' y atribuido a la key — el consumo por API aparece separado del chat interno en tu dashboard.

Forma del recurso (convención de recursos nuevos): id opaco run_<ULID> (ordenable por tiempo), object: "run", livemode (false en keys met_test_) y metadata libre.

Nota: el met del body es un hint informativo en v1 — el orquestador del workspace auto-rutea al especialista adecuado (mismo comportamiento que el chat). La gestión curada de Mets individuales llega con la fachada agents.

v1.0.0

Integraciones — activar y ejecutar conectores MCP por API

Las integraciones MCP de Met (Shopify, Alegra, etc.) llegan a la API pública. Activa conectores, guarda sus credenciales y ejecuta sus tools desde tu código. Aditivo. 251 → 256 operaciones públicas.

Nuevos endpoints (scopes integrations:read / integrations:manage / integrations:execute):

  • GET /workspaces/:id/integrations — catálogo + estado de conexión de cada integración.
  • GET /workspaces/:id/integrations/:key/tools — tools disponibles de una integración.
  • POST /workspaces/:id/integrations/:key/activate — activa la integración; opcional { credentials } (write-only).
  • DELETE /workspaces/:id/integrations/:key — desactiva.
  • POST /workspaces/:id/integrations/:key/tools/:tool — ejecuta una tool con el input del schema de la integración.

Reglas del camino público (importantes):

  • Credenciales write-only: se guardan pero ningún GET las devuelve, ni enmascaradas. El estado observable es connection_status / last_tested_at.
  • Errores del proveedor curados: si el tercero falla, recibes integration_upstream_error genérico — nunca el body crudo del proveedor (que podría traer credenciales o PII). El detalle queda en los logs internos.
  • Allowlist opcional por key: una key puede restringirse a ejecutar solo tools específicas (mínimo privilegio para ISVs).
  • Integración no activa → integration_not_active (409).
v1.0.0

Agents — crear y configurar Mets por API

Cierra el ciclo agéntico: ahora puedes crear y configurar Mets por API, no solo ejecutarlos. Con Runs (ejecutar) + Agents (gestionar) + Skills/Functions/Integraciones (equipar), el Met completo es operable por código. Aditivo. 256 → 263 operaciones públicas.

Nuevos endpoints (scopes agents:read / agents:write):

  • GET /workspaces/:id/agents — lista los Mets del workspace.
  • POST /workspaces/:id/agents — crea un Met (name, title, mission, instructions).
  • GET /agents/:id — detalle.
  • PATCH /agents/:id — actualiza nombre, instrucciones, misión o estado (active).
  • GET /agents/:id/skills — skills vinculadas al Met.
  • POST /agents/:id/skills/:skillId — vincula una skill.
  • DELETE /agents/:id/skills/:skillId — desvincula.

Diseño: es una fachada curada sobre el modelo interno de Mets — expone un contrato de "agente" limpio (name/instructions/mission) en vez de los item-endpoints crudos. Las skills se listan sin su prompt interno ni su config de conectores. La gestión fina de prompt (bloques, tools deshabilitadas) y los conectores MCP del Met llegan más adelante.

El ciclo completo por API: crear un Met (agents:write) → colgarle skills (agents:write) y conectores (integrations:manage) → ejecutarlo (runs:execute) → enterarte del resultado por webhook. Eso es infraestructura agéntica como servicio.

v1.0.0

Funciones IA, Grupos de Mets, Webhooks entrantes, Recordatorios y Actividad

Cierre de huecos detectados cruzando el menú del producto contra la superficie. Aditivo. 215 → 248 operaciones públicas.

Nuevos scopes:

  • functions:read / functions:write — Funciones IA (tools/código del Met): CRUD + link a un Met (8 ops).
  • agents:read / agents:write — ahora en uso: Grupos de Mets (7 ops). *(La gestión completa de Mets llega con la fachada agents en F1.)*
  • webhooks:manage — gestión de webhooks entrantes: CRUD, mappings, link a flujo, sample, rotate-secret (11 ops).
  • events:read — feed de actividad/eventos del workspace (3 ops).
  • reminders:read / reminders:writeRecordatorios (4 ops).

Notas de curación:

  • POST /webhooks/:id/rotate-secret devuelve el secreto nuevo una sola vez (mismo patrón que crear una key) — es el owner rotando su propio secreto de ingress.
  • GET /webhooks/:id/sample devuelve un payload de muestra del propio webhook del workspace.

Todavía interno (a propósito):

  • Gestión de Mets (crear/configurar Mets) y sus conectores MCP → llegan con la fachada agents en F1 (los Mets son items; se necesita una superficie curada, no decorar item-endpoints). Es el hueco #1 de la visión agéntica, registrado en el plan.