Skip to content

Automations API

Run your Agents API agents on a schedule or on a signed webhook through /v1.

An automation runs one of your agents without anyone sending it a message: on a schedule, or whenever a signed webhook arrives. Each time it fires, Dexto sends the agent the automation’s message, exactly as POST /v1/agents/{agent_id}/sessions/{session_id}/messages would, and the run streams, pauses for approvals and is charged like any other run.

An automation belongs to your API key’s Cloud project, and only that project’s agents can be its target. Use a developer API key, or the key Dexto gives a deployed Function or Site Worker as DEXTO_API_KEY. Every key of the project reaches every automation of the project; an automation of another project is 404. A CLI sign-in key gets 403.

POST /v1/automations
Terminal window
curl -X POST https://api.dexto.ai/v1/automations \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekday digest",
"trigger": {
"type": "schedule",
"schedule": { "type": "recurring", "cron": "0 9 * * 1-5", "timezone": "Europe/Berlin" }
},
"target": {
"type": "agent",
"agent_id": "'"$AGENT_ID"'",
"message": "Write today'"'"'s digest of new orders. It is {{firing.fired_at}}."
}
}'

The response is the automation, 201, with status: "active" and the schedule’s next_run_at.

A schedule is one of:

scheduleFires
{ "type": "once", "run_at": "…" }Once, within a minute of run_at (ISO 8601 with an offset, in the future, at most 366 days ahead). Then its status is completed.
{ "type": "recurring", "cron": "…", "timezone": "…" }On each occurrence of a five-field cron expression, read in an IANA timezone.

A recurring schedule may fire at most every 5 minutes: */5 * * * * is the most often, and 0,2 9 * * * is refused. The 5 minutes are real time, also on days the clocks change, and a firing that a clock change would bring within 5 minutes of the previous one is skipped. If Dexto falls behind, it fires once for the latest missed occurrence and carries on from the next one.

target.session says where each firing writes:

sessionEach firing
{ "mode": "new", "user": "…", "metadata": { … } } (default)Starts a new session with that end-user label and metadata.
{ "mode": "fixed", "session_id": "…" }Continues one session of the agent.

user defaults to automation:<automation id>, so GET /v1/agents/{agent_id}/sessions?user=… finds an automation’s sessions. metadata reaches the agent’s Function tools, as a session’s does. A fixed session runs one message at a time: a firing that comes while it still runs an earlier run is skipped.

target.message is a template of up to 8,000 characters. These placeholders are filled in when it fires, and nothing else is evaluated:

PlaceholderValue
{{automation.id}}The automation’s id.
{{automation.name}}Its name.
{{firing.id}}The firing’s id.
{{firing.source}}schedule, webhook or manual.
{{firing.fired_at}}When it fired, ISO 8601 in UTC.
{{firing.scheduled_for}}The schedule occurrence it fires; empty for other firings.
{{webhook.payload}}Webhook automations only: the request’s JSON body, up to 16,000 characters.

An unknown placeholder is refused with 400 when you save. A webhook payload comes from outside your code: tell the agent to treat it as data, not instructions.

Create the automation with "trigger": { "type": "webhook" }. The response carries its url in trigger and a webhook_secret, shown only this once:

{
"trigger": {
"type": "webhook",
"url": "https://api.dexto.ai/v1/automations/7d1c…/webhook"
},
"webhook_secret": "whsec_…"
}

Send JSON to the URL without an API key, signed with the secret. The Dexto-Signature header holds a Unix timestamp t and v1, the hex HMAC-SHA256 of <t>.<raw body> under the secret, the way Stripe signs its webhooks. The Cloud SDK builds the header for you with Web Crypto, so it works in Workers and the browser as well as Node:

import { signAutomationWebhook } from "@dexto-cloudflare/capabilities-client";
const body = JSON.stringify({ order_id: "1042", status: "paid" });
await fetch(automation.trigger.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Dexto-Signature": await signAutomationWebhook({
body,
secret: process.env.AUTOMATION_SECRET!,
}),
},
body,
});

Without the SDK, compute it yourself and sign the exact bytes you send:

import { createHmac } from "node:crypto";
const t = Math.floor(Date.now() / 1000);
const hex = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
const signature = `t=${t},v1=${hex}`;

The URL answers 202 with the firing as soon as it is recorded; the run starts right after. It refuses:

StatusWhen
401The signature is missing, does not match the body, or t is more than 5 minutes off.
400The body is not JSON. An empty body is allowed; its payload is null.
413The body is over 256 KiB.
409The automation is paused.
429The automation took 60 requests in the last minute; retry after Retry-After.
404No webhook automation has this id.

Sending the same signed request again (same t, same body) returns the first firing and starts nothing, so a captured request can’t be replayed. Rotate the secret with POST /v1/automations/{automation_id}/webhook/rotate_secret; the old one stops at once. The response carries the new secret in webhook_secret, the same field as the create response:

{
"object": "automation_webhook_secret",
"automation_id": "7d1c…",
"webhook_secret": "whsec_…"
}
GET /v1/automations/{automation_id}/firings

Firings are listed newest first, with next_after paging:

{
"object": "automation_firing",
"id": "5b0e…",
"automation_id": "7d1c…",
"source": "schedule",
"fired_at": "2026-10-16T07:00:00.412Z",
"scheduled_for": "2026-10-16T07:00:00.000Z",
"status": "completed",
"agent_id": "c2a9…",
"session_id": "e81f…",
"run_id": "3f0c…",
"error": null
}
statusMeaning
queuedRecorded; its run has not started yet.
runningIts run is running, or waiting for an approval.
completedIts run completed.
failedIts run failed (error.code is the run’s stop_reason), or it could not start: target_unavailable or dispatch_failed.
cancelledIts run was cancelled.
refusedIt started no run: error.code is insufficient_credits or spend_limit_reached.
skippedIt started no run: its fixed session was still busy (session_busy), or the automation paused itself just before (automation_paused).

The run itself carries trigger: { "type": "automation", "automation_id", "firing_id" }, on GET …/runs/{run_id} and on its run.started event, so your event handler can tell a firing’s run from a message’s.

Each firing is a run of the agent and is charged exactly as a message’s run: from your organization’s credits, toward the monthly spend limits on the agent and its Cloud project. Before starting the run, Dexto checks those limits and your credits; a firing they refuse is recorded as refused, with no session or run, never dropped silently.

An automation pauses itself, with a paused_reason, when firing again can’t help:

paused_reasonWhen
repeated_failuresIts last five finished firings since it was created or resumed each failed or were refused.
target_unavailableIts agent or Cloud project was archived, or its fixed session is gone.
requestedYou paused it.
OperationDoes
GET /v1/automationsLists the project’s automations, newest first; agent_id filters to one agent.
GET /v1/automations/{automation_id}Reads one.
PATCH /v1/automations/{automation_id}Changes name, target.message, target.session or the schedule. The agent and the trigger type can’t change. A new schedule makes a completed one-time automation active again.
POST /v1/automations/{automation_id}/pauseStops the schedule; the webhook answers 409.
POST /v1/automations/{automation_id}/resumeTurns it back on, from the next occurrence after now. A one-time automation whose time passed needs a new run_at first.
POST /v1/automations/{automation_id}/runFires it now, whatever its status, and returns the firing with 202.
DELETE /v1/automations/{automation_id}Deletes it and its firing history. Its runs stay readable on their sessions.

Automations an agent made with its automations tool show created_by: { "type": "agent", "agent_id": "…" }; yours show the API key that created them while it exists, otherwise you, as { "type": "user", "user_id": "…" }, including when you used an OAuth token.

A Cloud project holds at most 100 automations, whatever their status: paused and completed ones count, and a deleted one doesn’t. Creating the 101st is refused with 409; delete one you no longer need, then try again.

Firings are kept for 30 days, or for the number of days an Owner or Admin sets in the project’s retention settings in the Cloud app (1 to 3650). Dexto then deletes them, except each automation’s newest five and firings still waiting to start, so a rarely firing automation still shows its latest firings. The runs they started stay readable on their sessions, and an automation’s own last_fired_at stays. A run keeps its trigger (the automation and firing ids) after its firing is deleted, until the automation itself is deleted, when trigger reads null. Its firing_id may then name a firing that is no longer listed.

A data deletion for an end user also deletes the automations whose sessions carry that user’s label, and one for an agent deletes all of its automations.

In the Dexto app, open Cloud, then a project: its automations are listed with its other resources, for example Every 5 minutes · next 15:35 UTC or Webhook · paused (repeated_failures). Open one to see its trigger and next run, its agent and its firings with the status and error of each run, and to pause, resume, run or delete it. Everyone in your organization can see an automation; the person whose key created it, Owners and Admins change it. The app never shows a webhook’s signing secret.

Sites, Workers and Functions you build in Dexto include the Cloud SDK, which wraps every operation above as automations:

import { createCapabilitiesClient } from "@dexto-cloudflare/capabilities-client";
const dexto = createCapabilitiesClient({ apiKey: env.DEXTO_API_KEY });
const automation = await dexto.automations.create({
name: "Weekday digest",
trigger: {
type: "schedule",
schedule: { type: "recurring", cron: "0 9 * * 1-5", timezone: "Europe/Berlin" },
},
target: { type: "agent", agent_id: agent.id, message: "Write today's digest." },
});
const firing = await dexto.automations.run(automation.id);
const { data: firings } = await dexto.automations.listFirings(automation.id, { limit: 10 });
await dexto.automations.pause(automation.id);

The helpers are create, list, get, update, delete, pause, resume, run, rotateWebhookSecret and listFirings, each taking and returning the shapes above, in the same snake_case. create and rotateWebhookSecret return a webhook’s webhook_secret; keep it as a secret. signAutomationWebhook({ body, secret, timestamp? }) builds the Dexto-Signature value for a webhook request.

See /v1/openapi.json for every request and response schema, error and rate limit. Automation requests share the agents rate limit.