Automations
The *when* of your workspace: wire an event to an action and let Meteor fire it without anyone calling the API.
An automation joins two things: a trigger_type — the event that wakes it up — and an action_kind — what it does when that happens. It is the *when* of the agentic layer: Functions and flows holds the logic, Tasks holds the procedure, and this decides what sets them off.
Uses the automations:read and automations:write scopes, plus automations:execute to fire one on demand.
The model in one sentence
A type from the catalog, some conditions, and an action. That's all:
const auto = await met.automations.create({
name: 'Publish when the deal is won',
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="Publish when the deal is won",
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": "Publish when the deal is won",
"trigger_type": "item.field_changed",
"conditions": { "collection_id": 42, "field": "estado", "to": "ganado" },
"action_kind": "flow",
"flow_id": "f1e2d3c4-0000-4000-8000-123456789abc"
}'It is born enabled unless you send enabled: false. The optional fields are description, icon, collection_id, action_config and enabled.
The catalog rules
trigger_type is not free text: it comes from the catalog. Read it before you build anything, because it is also the list of what you can offer your own user.
const catalog = await met.automations.catalog();catalog = met.automations.catalog()curl https://api.met.meteor.com.co/api/v1/trigger-catalog \
-H "Authorization: Bearer $MET_API_KEY"Every entry carries type (what goes in trigger_type), category, label, description, icon, conditions_schema — what you must fill in —, exposes — the variables the action receives when it fires — and enabled.
enabled: falseis not a missing permission. Those are types published in the catalog that the dispatcher does not react to yet. You can read them; creating one answers400. Filter byenabledbefore drawing options in your own interface.
The types that fire today, with their required conditions:
trigger_type | Fires when | Required conditions |
|---|---|---|
item.created | A new item lands in a collection. | collection_id |
item.field_changed | An item field moves to a specific value. | collection_id, field, to |
item.updated | The item changes, whatever the field. | collection_id |
item.deleted | An item is deleted from the collection. | collection_id |
contact.created | A contact enters the CRM (by hand, through a channel, or over the API). | — |
contact.field_updated | A contact field changes. | field, operator |
date_time | An exact instant arrives. Once only. | scheduled_date (ISO 8601) |
recurrent | The cron pattern comes around. | recurrence_pattern |
field_datetime | A while before, at, or after a date field. | source, field_id, direction |
webhook.received | A POST reaches one of your inbound webhooks. | endpoint_slug |
manual | Only on request. See the note below. | — |
On create, only the required conditions are checked for presence; the rest of the object travels as is and each type interprets it. If one is missing, the response tells you which: Condición "collection_id" requerida para trigger_type "item.created".
recurrence_pattern takes a five-part cron (0 9 * * 1) or one of these aliases: hourly (on the hour), daily (9:00), weekly (Monday 9:00) and monthly (1st of the month, 9:00). The pattern is evaluated in your workspace's time zone, and if it has none set, in Colombian time.
manualhas no API trigger. You can create it, butrun-nowcovers managed processes and recurring flows only (see below). If you need to start something from your code whenever you decide to, run the flow directly withmet.flows.run(flowId)or the task withmet.tasks.execute(taskId).
What it runs: action_kind
action_kind | What it does | What you must give it |
|---|---|---|
task | Runs a task in the workspace. | task_id |
flow | Runs a published flow. | flow_id |
ai_field | Fills a collection field using AI. | collection_id and action_config.field |
If you omit action_kind, task is assumed — and then task_id becomes required.
The flow has to be published. Wiring one that only has a draft answers
400: publish it first withmet.flows.publish(flowId)— see Functions and flows. The task or flow also has to belong to your own workspace.
Read, enable, disable and delete
const all = await met.automations.list();
const forOneTask = await met.automations.list({ taskId: 'd4c3b2a1-…' });
const one = await met.automations.retrieve(auto.id);
await met.automations.setEnabled(auto.id, false); // turn it off
await met.automations.update(auto.id, { conditions: { collection_id: 42, field: 'estado', to: 'perdido' } });
await met.automations.remove(auto.id);all_of_them = met.automations.list()
for_one_task = met.automations.list(task_id="d4c3b2a1-…")
one = 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() is a shortcut for update(): it sends enabled alone and leaves the rest of the configuration untouched.
Two things the listing returns that are worth checking before you conclude something is broken:
nameandiconalways come resolved. If you gave it no name, it falls back to the target task or flow, and finally to(sin nombre); the icon falls back to the trigger type's own.task_nameandflow_namesave you the second call to find out what each row runs.
When you are not the one who paused it
An automation can come back disabled without anyone touching it. It happens to the ones that run a flow: if a single automation runs away and eats the workspace's daily run ceiling on its own, Meteor pauses that automation and writes down why.
const a = await met.automations.retrieve(id);
if (!a.enabled && a.paused_reason) {
// the system stopped it; a.paused_reason says why
}
paused_reason: null together with enabled: false means the opposite: a person turned it off. Switching it back on with enabled: true clears the reason and gives it a fresh count.
What matters for your integration: what gets paused is the automation at fault, not the workspace. The rest keep running.
Managed processes
Some rows in the listing arrive with is_system_managed: true. Those are processes Meteor publishes and versions — reports, reminders, tools a Met uses inside a conversation. Their definition is not edited over PATCH, and update() with any field other than enabled answers 400. They cannot be deleted either.
In exchange, they have a surface of their own. Before using it, look at what the row itself declares:
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; // does it accept running on demand?
p.last_run; // the latest run, already resolved
The six methods that follow are only for these rows. On an automation you created they answer
400, and that is the contract, not a problem with your key.
Run history
const runs = await met.automations.runs(id, { limit: 50 });
// [{ id, source: 'scheduled' | 'manual', status, run_key, result, error_message, started_at, finished_at }, …]runs = 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"Newest first. limit goes from 1 to 100; without it, 20. run_key is the key of the occurrence (for example 2026-08-14): it is what keeps the schedule from running the same thing twice.
For the history of one of your own flows the route is a different one: met.flows.listRuns(flowId).
Run on demand
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 means that occurrence had already run and your request duplicated nothing.
The response has two shapes, and mixing them up costs an afternoon:
- If the process runs a system job (
action_kind: 'system_job'),runis the real run: the sameidyou will later see inruns(). - If it runs a flow (
action_kind: 'flow'),runcomes back withstatus: 'processing'and anidthat belongs to the flow run, not to a managed run. Thatidnever shows up inruns(). To follow it:
const flowRun = await met.flows.findRun(run.id);
It answers 400 if the process is paused, if it does not accept running on demand (manual_run is false), or if it is a flow that is not recurring.
Change the schedule
await met.automations.updateSchedule(id, '0 12 * * 1-6'); // 12:00, Monday to Saturdaymet.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"}'The cron is deliberately narrow: five parts, with a fixed hour and days of the week. Day of month and month must be *; anything else answers 400. And it only applies if capabilities.editable_fields includes schedule — a process that fires on an event has no schedule to change.
Edit without overwriting anyone
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, // disconnects the follow-up flow
});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 is the revision the row carried when you read it. If someone changed it in between, the call fails with 409 instead of overwriting their change; the SDKs surface it as MetInvalidRequestError with status 409. Re-read the process and try again with the new revision.
Send at least one of the two configurable fields: with neither, it answers 400. flow_id: null is explicit and is sent — it disconnects the flow.
Undo, and see who changed what
await met.automations.restoreConfiguration(id, { expected_revision: p.managed_revision });
const changes = 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"])
changes = met.automations.configuration_history(automation_id, limit=20)restoreConfiguration() returns the process to the configuration Meteor published, and only applies if capabilities.can_restore is true. The history goes newest first, with limit between 1 and 100 (without it, 20).
Scheduling a task: use this API, not the task's own
There is an older route for hanging a trigger off a task from the task itself (POST /tasks/{taskId}/triggers). It is still published for compatibility, but what it creates is not tied to a workspace, so it does not show up in GET /workspaces/{workspaceId}/automations and it does not go through the catalog's validation.
To schedule a task today, create an automation that points at it:
await met.automations.create({
name: 'Qualify leads every Monday',
trigger_type: 'recurrent',
conditions: { recurrence_pattern: '0 9 * * 1' },
action_kind: 'task',
task_id: task.id,
});
Errors you will see
| Response | What causes it |
|---|---|
400 trigger_type "…" no existe en el catálogo | The type is not in GET /trigger-catalog. |
400 …todavía no está habilitado en producción | The type exists but came back with enabled: false. |
400 Condición "…" requerida para trigger_type "…" | A required condition is missing. |
400 El flow no ha sido publicado | You wired a flow that only has a draft. |
400 La task no pertenece a este workspace | The task or flow belongs to another workspace. |
400 Esta automatización no tiene historial administrado | You called runs() on an automation of your own. |
400 Los procesos administrados solo permiten activar o pausar | An update() with fields other than enabled on a managed process. |
409 La configuración cambió en otra sesión | Your expected_revision went stale. |
The rest — 401 for a wrong key or workspace, 429 for limits — behaves like everywhere else in the API: see Errors, idempotency and pagination.