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.
Create an automation
Section titled “Create an automation”/v1/automations 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:
schedule | Fires |
|---|---|
{ "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.
Choose the session
Section titled “Choose the session”target.session says where each firing writes:
session | Each 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.
Write the message
Section titled “Write the message”target.message is a template of up to 8,000 characters. These placeholders are filled in when it
fires, and nothing else is evaluated:
| Placeholder | Value |
|---|---|
{{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.
Fire on a webhook
Section titled “Fire on a webhook”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:
| Status | When |
|---|---|
401 | The signature is missing, does not match the body, or t is more than 5 minutes off. |
400 | The body is not JSON. An empty body is allowed; its payload is null. |
413 | The body is over 256 KiB. |
409 | The automation is paused. |
429 | The automation took 60 requests in the last minute; retry after Retry-After. |
404 | No 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_…"}See what fired
Section titled “See what fired”/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}status | Meaning |
|---|---|
queued | Recorded; its run has not started yet. |
running | Its run is running, or waiting for an approval. |
completed | Its run completed. |
failed | Its run failed (error.code is the run’s stop_reason), or it could not start: target_unavailable or dispatch_failed. |
cancelled | Its run was cancelled. |
refused | It started no run: error.code is insufficient_credits or spend_limit_reached. |
skipped | It 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.
Billing and failures
Section titled “Billing and failures”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_reason | When |
|---|---|
repeated_failures | Its last five finished firings since it was created or resumed each failed or were refused. |
target_unavailable | Its agent or Cloud project was archived, or its fixed session is gone. |
requested | You paused it. |
Manage automations
Section titled “Manage automations”| Operation | Does |
|---|---|
GET /v1/automations | Lists 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}/pause | Stops the schedule; the webhook answers 409. |
POST /v1/automations/{automation_id}/resume | Turns 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}/run | Fires 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.
Limits and retention
Section titled “Limits and retention”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.
See them in the Cloud app
Section titled “See them in the Cloud app”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.
Use the Cloud SDK
Section titled “Use the Cloud SDK”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.