Automatizaciones
El *cuándo* de tu workspace: conecta un evento con una acción y deja que Meteor la dispare sin que nadie llame a la API.
Una automatización une dos cosas: un trigger_type —el evento que la despierta— y un action_kind —lo que hace cuando eso pasa—. Es el *cuándo* del plano agéntico: Funciones y flujos pone la lógica, Tareas pone el procedimiento, y esto decide qué los enciende.
Usa los scopes automations:read, automations:write y, para disparar a mano, automations:execute.
El modelo en una frase
Un tipo del catálogo, unas condiciones y una acción. Nada más:
const auto = await met.automations.create({
name: 'Publicar cuando el deal se gana',
trigger_type: 'item.field_changed',
conditions: { collection_id: 42, field: 'estado', to: 'ganado' },
action_kind: 'flow',
flow_id: 'f1e2d3c4-0000-4000-8000-123456789abc',
});
console.log(auto.id, auto.enabled); // → '…', trueauto = met.automations.create(
name="Publicar cuando el deal se gana",
trigger_type="item.field_changed",
conditions={"collection_id": 42, "field": "estado", "to": "ganado"},
action_kind="flow",
flow_id="f1e2d3c4-0000-4000-8000-123456789abc",
)
print(auto["id"], auto["enabled"])curl -X POST https://api.met.meteor.com.co/api/v1/workspaces/7/automations \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Publicar cuando el deal se gana",
"trigger_type": "item.field_changed",
"conditions": { "collection_id": 42, "field": "estado", "to": "ganado" },
"action_kind": "flow",
"flow_id": "f1e2d3c4-0000-4000-8000-123456789abc"
}'Nace encendida salvo que mandes enabled: false. Los campos opcionales son description, icon, collection_id, action_config y enabled.
El catálogo manda
trigger_type no es texto libre: sale del catálogo. Léelo antes de construir nada, porque es también la lista de lo que puedes ofrecerle a tu usuario.
const catalogo = await met.automations.catalog();catalogo = met.automations.catalog()curl https://api.met.meteor.com.co/api/v1/trigger-catalog \
-H "Authorization: Bearer $MET_API_KEY"Cada entrada trae type (lo que va en trigger_type), category, label, description, icon, conditions_schema —qué hay que llenarle—, exposes —qué variables recibe la acción cuando se dispara— y enabled.
enabled: falseno es un permiso que te falte. Son tipos publicados en el catálogo a los que el despachador todavía no reacciona. Se leen; crearlos responde400. Filtra porenabledantes de pintar opciones en tu propia interfaz.
Los tipos que hoy disparan, con sus condiciones obligatorias:
trigger_type | Se dispara cuando | Condiciones obligatorias |
|---|---|---|
item.created | Entra un ítem nuevo a una colección. | collection_id |
item.field_changed | Un campo del ítem pasa a un valor concreto. | collection_id, field, to |
item.updated | El ítem cambia, sin importar qué campo. | collection_id |
item.deleted | Se borra un ítem de la colección. | collection_id |
contact.created | Entra un contacto al CRM (manual, por canal o por API). | — |
contact.field_updated | Cambia un campo del contacto. | field, operator |
date_time | Llega un instante exacto. Una sola vez. | scheduled_date (ISO 8601) |
recurrent | Toca el patrón cron. | recurrence_pattern |
field_datetime | Un tiempo antes, en, o después de un campo de fecha. | source, field_id, direction |
webhook.received | Llega un POST a un webhook entrante tuyo. | endpoint_slug |
manual | Solo a pedido. Ver la nota más abajo. | — |
Al crear se verifica que las condiciones obligatorias estén presentes; el resto del objeto viaja tal cual y lo interpreta cada tipo. Si te falta una, la respuesta te dice cuál: Condición "collection_id" requerida para trigger_type "item.created".
recurrence_pattern acepta un cron de cinco partes (0 9 * * 1) o uno de estos alias: hourly (en punto, cada hora), daily (9:00), weekly (lunes 9:00) y monthly (día 1, 9:00). El patrón se evalúa en la zona horaria de tu workspace, y si no tiene una configurada, en hora de Colombia.
manualno tiene disparo por API. Se puede crear, perorun-nowcubre únicamente procesos administrados y flujos recurrentes (más abajo). Si necesitas arrancar algo desde tu código cuando tú decidas, ejecuta el flujo directo conmet.flows.run(flowId)o la tarea conmet.tasks.execute(taskId).
Qué ejecuta: action_kind
action_kind | Qué hace | Qué le tienes que dar |
|---|---|---|
task | Ejecuta una tarea del workspace. | task_id |
flow | Ejecuta un flujo publicado. | flow_id |
ai_field | Rellena un campo de la colección con IA. | collection_id y action_config.field |
Si omites action_kind, se asume task — y entonces task_id pasa a ser obligatorio.
El flujo tiene que estar publicado. Conectar uno que solo tiene borrador responde
400: publícalo antes conmet.flows.publish(flowId)— ver Funciones y flujos. La tarea o el flujo, además, tienen que ser de tu mismo workspace.
Leer, encender, apagar y borrar
const todas = await met.automations.list();
const deUnaTarea = await met.automations.list({ taskId: 'd4c3b2a1-…' });
const una = await met.automations.retrieve(auto.id);
await met.automations.setEnabled(auto.id, false); // apagar
await met.automations.update(auto.id, { conditions: { collection_id: 42, field: 'estado', to: 'perdido' } });
await met.automations.remove(auto.id);todas = met.automations.list()
de_una_tarea = met.automations.list(task_id="d4c3b2a1-…")
una = met.automations.retrieve(auto["id"])
met.automations.set_enabled(auto["id"], False)
met.automations.update(auto["id"], conditions={"collection_id": 42, "field": "estado", "to": "perdido"})
met.automations.remove(auto["id"])curl https://api.met.meteor.com.co/api/v1/workspaces/7/automations \
-H "Authorization: Bearer $MET_API_KEY"
curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'setEnabled() es un atajo de update(): manda solo enabled y no toca el resto de la configuración.
Dos cosas que el listado devuelve y conviene mirar antes de creer que algo no anda:
nameeiconsiempre vienen resueltos. Si no le pusiste nombre, cae al de la tarea o el flujo destino, y en último caso a(sin nombre); el ícono cae al del tipo de disparador.task_nameyflow_namete ahorran la segunda llamada para saber qué ejecuta cada fila.
Cuando la pausa no la pusiste tú
Una automatización puede volver apagada sin que nadie la haya tocado. Pasa con las que ejecutan un flujo: si una sola se desboca y consume por sí misma el techo de corridas del día del workspace, Meteor pausa esa automatización y deja el motivo escrito.
const a = await met.automations.retrieve(id);
if (!a.enabled && a.paused_reason) {
// la frenó el sistema; a.paused_reason dice por qué
}
paused_reason: null con enabled: false significa lo contrario: la apagó una persona. Volver a encenderla con enabled: true limpia el motivo y le da cuenta nueva.
Lo importante para tu integración: se pausa la automatización culpable, no el workspace. Las demás siguen corriendo.
Procesos administrados
Algunas filas del listado llegan con is_system_managed: true. Son procesos que publica y versiona Meteor —reportes, recordatorios, herramientas que un Met usa dentro de una conversación—: su definición no se edita por PATCH, y update() con cualquier campo que no sea enabled responde 400. Tampoco se borran.
A cambio, tienen una superficie propia. Antes de usarla, mira lo que la propia fila declara:
const p = await met.automations.retrieve(id);
p.is_system_managed; // true
p.capabilities.editable_fields; // ['schedule', 'flow']
p.capabilities.locked_fields; // ['handler', 'tool_name', 'templates', 'recipients']
p.capabilities.can_restore; // true
p.managed_revision; // 2
p.manual_run; // ¿admite ejecución a pedido?
p.last_run; // la última corrida, ya resuelta
Los seis métodos que siguen son solo para estas filas. Sobre una automatización que creaste tú responden
400, y eso es el contrato, no un problema de tu key.
Historial de corridas
const corridas = await met.automations.runs(id, { limit: 50 });
// [{ id, source: 'scheduled' | 'manual', status, run_key, result, error_message, started_at, finished_at }, …]corridas = met.automations.runs(automation_id, limit=50)curl "https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/runs?limit=50" \
-H "Authorization: Bearer $MET_API_KEY"De la más reciente hacia atrás. limit va entre 1 y 100; sin él, 20. run_key es la llave de la ocurrencia (por ejemplo 2026-08-14): es lo que hace que el horario no corra dos veces lo mismo.
Para el historial de un flujo tuyo, la ruta es otra: met.flows.listRuns(flowId).
Ejecutar a pedido
const { ok, already_processed, run } = await met.automations.runNow(id, {
input: { periodo: '2026-08-14' },
});res = met.automations.run_now(automation_id, input={"periodo": "2026-08-14"})curl -X POST https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/run-now \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":{"periodo":"2026-08-14"}}'already_processed: true significa que esa ocurrencia ya estaba corrida y tu pedido no duplicó nada.
La respuesta tiene dos formas y confundirlas cuesta una tarde:
- Si el proceso ejecuta un trabajo del sistema (
action_kind: 'system_job'),runes la corrida real: el mismoidque vas a ver después enruns(). - Si ejecuta un flujo (
action_kind: 'flow'),runviene constatus: 'processing'y unidque es el del flow run, no el de una corrida administrada. Eseidno aparece nunca enruns(). Para seguirlo:
const flowRun = await met.flows.findRun(run.id);
Responde 400 si el proceso está pausado, si no admite ejecución a pedido (manual_run en false) o si es un flujo que no es recurrente.
Cambiar el horario
await met.automations.updateSchedule(id, '0 12 * * 1-6'); // 12:00, de lunes a sábadomet.automations.update_schedule(automation_id, "0 12 * * 1-6")curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/schedule \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recurrence_pattern":"0 12 * * 1-6"}'El cron es acotado a propósito: cinco partes, con una hora fija y días de la semana. El día del mes y el mes tienen que ir en *; cualquier otra cosa responde 400. Y solo aplica si capabilities.editable_fields incluye schedule — un proceso que se activa por un evento no tiene horario que cambiar.
Editar sin pisar a nadie
const p = await met.automations.retrieve(id);
await met.automations.updateConfiguration(id, {
expected_revision: p.managed_revision,
recurrence_pattern: '0 7 * * 1-5',
flow_id: null, // desconecta el flujo posterior
});p = met.automations.retrieve(automation_id)
met.automations.update_configuration(
automation_id,
expected_revision=p["managed_revision"],
recurrence_pattern="0 7 * * 1-5",
flow_id=None,
)curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/configuration \
-H "Authorization: Bearer $MET_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expected_revision":2,"recurrence_pattern":"0 7 * * 1-5","flow_id":null}'expected_revision es la revisión que traía la fila cuando la leíste. Si alguien la cambió en el medio, la llamada falla con 409 en vez de pisar el cambio ajeno; en los SDK llega como MetInvalidRequestError con status 409. Relee el proceso y vuelve a intentar con la revisión nueva.
Manda al menos uno de los dos campos configurables: sin ninguno responde 400. flow_id: null es explícito y sí se envía — desconecta el flujo.
Deshacer y ver quién tocó qué
await met.automations.restoreConfiguration(id, { expected_revision: p.managed_revision });
const cambios = await met.automations.configurationHistory(id, { limit: 20 });
// [{ revision, operation: 'update' | 'restore', changed_by_user_id, before_config, after_config, created_at }, …]met.automations.restore_configuration(automation_id, expected_revision=p["managed_revision"])
cambios = met.automations.configuration_history(automation_id, limit=20)restoreConfiguration() devuelve el proceso a la configuración que publicó Meteor y solo aplica si capabilities.can_restore es true. El historial va de lo más reciente hacia atrás, con limit entre 1 y 100 (sin él, 20).
Programar una tarea: usa esta API, no la de la tarea
Existe una ruta anterior para colgarle un disparador a una tarea desde la tarea misma (POST /tasks/{taskId}/triggers). Sigue publicada por compatibilidad, pero lo que crea no queda atado a un workspace, así que no aparece en GET /workspaces/{workspaceId}/automations ni pasa por la validación del catálogo.
Para programar una tarea hoy, crea una automatización que apunte a ella:
await met.automations.create({
name: 'Calificar leads cada lunes',
trigger_type: 'recurrent',
conditions: { recurrence_pattern: '0 9 * * 1' },
action_kind: 'task',
task_id: tarea.id,
});
Errores que vas a ver
| Respuesta | Qué la causa |
|---|---|
400 trigger_type "…" no existe en el catálogo | El tipo no está en GET /trigger-catalog. |
400 …todavía no está habilitado en producción | El tipo existe pero llegó con enabled: false. |
400 Condición "…" requerida para trigger_type "…" | Falta una condición obligatoria. |
400 El flow no ha sido publicado | Conectaste un flujo que solo tiene borrador. |
400 La task no pertenece a este workspace | La tarea o el flujo son de otro workspace. |
400 Esta automatización no tiene historial administrado | Llamaste a runs() sobre una automatización tuya. |
400 Los procesos administrados solo permiten activar o pausar | Un update() con campos que no son enabled sobre un proceso administrado. |
409 La configuración cambió en otra sesión | Tu expected_revision quedó viejo. |
El resto —401 por key o workspace equivocado, 429 por límites— se comporta igual que en toda la API: ver Errores, idempotencia y paginación.