Skip to content

Agents API

Define agents and run them for your own users through /v1.

The Agents API runs a Dexto agent for your own customers, from your backend. You define the agent once: its instructions, model and tools. Then you start a session for each of your users and send their messages. Dexto runs the agent loop, streams the reply, and pauses for your approval when a tool needs it.

An agent you create here is separate from your own Dexto agents. Its sessions never appear in your chats, it can’t see your files, memory or connections, and its runs get only the tools you list. Call the API from a server with your API key and https://api.dexto.ai; never put the key in a browser.

Use a developer API key, or the key Dexto gives a deployed Function or Site Worker as DEXTO_API_KEY. A deployed key acts as its deployment’s Cloud project, not as the person who deployed it, so a deployed backend can create and run agents without a key pasted into its settings. A CLI sign-in key gets 403.

POST /v1/agents
Terminal window
curl -X POST https://api.dexto.ai/v1/agents \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme support",
"instructions": "You help Acme customers with orders. Be brief and friendly.",
"tools": [{ "type": "web_search" }]
}'

The response is the agent, with an id and version: 1. Leave model out to use Dexto’s default model, or set it to an id from GET /v1/models that supports tool calls.

GET /v1/agents lists your agents, newest first, and GET /v1/agents/{agent_id} reads one.

An agent’s tools are the only tools its runs get. There are eight kinds:

ToolWhat it does
web_searchSearches the web.
web_fetchReads the text of up to 10 web pages by URL.
filesFinds, reads, searches, writes, edits and shares your project’s files.
functionCalls named operations of a Function you own.
sandboxStarts a sandbox, works with its files and runs commands in it.
mediaGenerates images, audio or video.
automationsSchedules follow-ups for itself, such as “check again tomorrow at 9”.
mcpCalls tools of a remote MCP server, with your key attached by Dexto.

Each search, fetch, Function call, MCP tool call and media generation is charged to the agent’s project like the Search and fetch API, Function invocations and the Media API, and files it writes are stored like any file. When the agent’s credits or spend limit refuse a call, the tool returns an error with insufficient_credits or spend_limit_reached instead of running, and the run goes on.

A files tool works with your API key’s Cloud project’s files. prefix limits it to paths that start with it (end it with /; leave it out for every file), and read_only gives it the reading tools only:

{ "type": "files", "prefix": "knowledge/", "read_only": true }

The agent sees the prefix as its root, /: with the prefix knowledge/, the file knowledge/refunds.md is /refunds.md to it. Every path the tools return starts with /, and a path starting with / always means that path from the root, so a returned path always names the same file again. Without the leading /, a path that starts with knowledge/ means the same file too, as your instructions may name it. The agent can’t name a path above /. A files tool gives the model these tools, with the names and arguments of the file tools Dexto agents use on a computer:

Model toolArgumentsWhat it does
read_filefile_path, offset, limitReads a text file, up to 2,000 lines or 256 KiB per call, from line offset. A binary file, or text over 1 MiB, comes back as a 15-minute URL.
read_media_filefile_pathShows the model an image or PDF, up to 16 MiB; a larger one comes back as a URL.
glob_filespattern, afterFinds files by a glob such as **/*.md, 200 per page.
search_filespattern, include, fixedString, afterSearches text files line by line with a regular expression (ripgrep syntax) or a fixed string; up to 200 files or 16 MiB per call.
write_filefile_path, contentWrites UTF-8 text up to 1 MiB, replacing any file at that path.
edit_filefile_path, old_string, new_string, replace_allReplaces text that must match exactly once, unless replace_all; nothing is written if the file changed since it was read.
delete_filefile_pathDeletes one file.
share_filefile_path, expires_inCreates a download link for a person, for 1 second to 24 hours (one hour by default).

A read-only files tool has only read_file, read_media_file, glob_files and search_files. require_approval asks before each write, edit, delete and share; reads never ask. A refused call returns an error that says what to do next, and the run goes on. Files the agent writes record it as their author, and their storage is charged to the project. Reading and searching are free.

A Function tool names the Function’s project_id, a ready deployment_id and the operations the agent may call:

{
"type": "function",
"name": "orders",
"project_id": "7f1b5a4e-…",
"deployment_id": "0c9d2e11-…",
"operations": ["lookup_order", "refund_order"],
"require_approval": true
}

Each operation becomes a tool the model sees as <name>_<operation>, for example orders_refund_order. Its input schema comes from your Function’s OpenAPI document when you save the agent. Calls run with your authority against that exact deployment. To change the deployment, save the agent again with the new deployment_id.

A sandbox tool works in your API key’s Cloud project, in one of two ways. It can name a sandbox of the project that every run shares:

{
"type": "sandbox",
"sandbox": { "type": "project", "sandbox_id": "5d0c7a90-…" }
}

Or it can give each session a sandbox of its own, of size large, the only size for now:

{ "type": "sandbox", "sandbox": { "type": "session", "size": "large" } }

A session sandbox is created the first time the session uses the tool. It is deleted, with its files, when the session ends: when you archive the agent, or after the session has had no run for 24 hours. The next run then starts with a fresh one. Session sandboxes appear in GET /v1/sandboxes as agent-session-<session id>. The agent starts and pauses the sandbox itself, and a file or command call starts a stopped sandbox first. Its running time is paid from credits like any sandbox, and an idle sandbox is paused, so idle time is not charged.

When the agent also has a files tool, its sandbox tool can copy files: copy_from_files copies files (by path, or every file in a folder) into a /workspace directory, and copy_to_files saves a sandbox file as a project file. Both name files as the files tools do, relative to the files tool’s prefix, and a read-only files tool can’t save. Copies are free.

A sandbox tool can also use your project’s secrets without seeing them. Name them in secrets:

{
"type": "sandbox",
"sandbox": { "type": "session", "size": "large" },
"secrets": ["STRIPE_KEY"]
}

Each command then has $STRIPE_KEY in its environment, holding a placeholder. Requests the command makes over HTTPS to the secret’s hosts go through Dexto, which puts the real value on them as the secret’s placement says, and removes the value from the response. Requests to any other host get nothing, so a command can’t send the key elsewhere. Use the variable as you would the real key: curl -H "Authorization: Bearer $STRIPE_KEY" https://api.stripe.com/v1/charges. A sandbox tool uses at most one secret per host. The access lasts only while the command runs, and commands with secrets in one sandbox take turns. In a sandbox that several agents share, a command of another agent running at that moment could reach the same hosts with the key attached; give an agent with secrets its own sandbox, or a session sandbox, to avoid that.

A media tool lets the agent generate the kinds of media you name in kinds: image, audio or video. Leave kinds out to allow all three.

{ "type": "media", "kinds": ["image"], "require_approval": true }

The agent lists the models of those kinds, gets a price estimate for its input, then generates from the estimate, with the same models and estimates as the Media API. A model of another kind is refused. A generation starts only when credits and spend limits cover its estimate, and is charged its actual cost. An image is usually ready within the call. A longer generation, such as a video, comes back with its status and an id the agent checks later. A result has the shape of a Media API generation: each output has a url, a download link the agent can give your user, valid for up to an hour. Outputs are deleted 24 hours after they are made. When the agent also has a files tool, generate takes save_to (a file path, or a prefix ending in /, inside the files tool’s prefix) to keep the outputs as project files, and each saved output names the exact path it was saved at in file_path; a read-only files tool can’t save. The agent’s generations belong to its project: your project’s keys list and read them, with fresh links, through GET /v1/media/generations, while keys of other projects can’t. With require_approval, the run waits for your OK before each generation; listing models and estimating don’t.

An automations tool lets the agent run itself again later: once at a time, or on a cron schedule no more often than every 5 minutes, with a message it writes now for its future self.

{ "type": "automations", "max_active": 5 }

The model gets one automations tool with the actions create, list, pause, resume and delete. A follow-up continues the current session, or starts a new session for the same end user and metadata each time. Each is an automation of the agent’s project that runs only this agent, created by the agent: you list and manage them through /v1/automations, and the agent reaches only the ones it created, never yours. It keeps at most max_active active at once (10 when you leave it out, at most 100); past that, create tells the model to pause or delete one first. Each firing is a run of the agent, paid like any other. With require_approval, the run waits for your OK before each create and resume.

An MCP tool calls tools of a remote MCP server (Streamable HTTP, over HTTPS on a public host) from Dexto’s servers:

{
"type": "mcp",
"name": "linear",
"server_url": "https://mcp.linear.app/mcp",
"auth": { "type": "secret", "secret": "LINEAR_KEY" },
"tools": ["list_issues", "create_issue"],
"require_approval": true
}

auth is null for a server that needs no credential, or names one of your project’s secrets whose hosts include the server’s host. When you save the agent, Dexto connects to the server with the secret, lists its tools, refuses a name the server doesn’t publish and keeps each listed tool’s input schema with the version. Each becomes a model tool <name>_<tool>, for example linear_create_issue; to pick up a changed schema, save the agent again. Calls carry the secret only to the server’s own address, follow no redirects, time out after 30 seconds and return at most 256 KiB; the secret’s value is removed from results. Each call the server answers is charged a flat price, like a Function call. A server that can’t be reached, a refused key or a deleted secret comes back as a tool error, and the run goes on.

Set require_approval: true on any tool that should wait for your OK before it runs.

PUT /v1/agents/{agent_id}

PUT replaces the whole definition and creates the next version. Sessions that already exist keep the version they started with. Send expected_version to reject the update if someone else changed the agent first; the API then returns 409 with the current version.

DELETE /v1/agents/{agent_id} archives the agent. It accepts no new sessions or messages, and its history stays readable.

POST /v1/agents/{agent_id}/sessions
Terminal window
curl -X POST "https://api.dexto.ai/v1/agents/$AGENT_ID/sessions" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "user": "customer_42" }'

user is your own identifier for the person. Dexto stores it as a label, so you can list a user’s sessions with GET /v1/agents/{agent_id}/sessions?user=customer_42; it doesn’t sign anyone in. A session uses the agent’s current version, or the one you pass as version.

Lists return a page at a time, newest first. When has_more is true, pass the page’s next_after as after to read the next one; it is null on the last page.

Pass metadata when you start a session, for example your tenant id:

{ "user": "customer_42", "metadata": { "tenant_id": "acme", "plan": "pro" } }

Each time the agent calls one of your Function tools, Dexto sends the session’s user and metadata, plus the agent, session and run ids, as trusted context. Your Function reads it as context.caller. The model can’t set or change it, so scope data by context.caller instead of any customer id the model put in the tool input. Metadata is up to 4 KiB of JSON and fixed for the session.

Dexto keeps your organizations apart; keeping your own customers apart is up to your backend. Check that a session belongs to the signed-in user before relaying it, and let your Functions enforce access with context.caller.

POST /v1/agents/{agent_id}/sessions/{session_id}/messages
Terminal window
curl -X POST "https://api.dexto.ai/v1/agents/$AGENT_ID/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $REQUEST_ID" \
-d '{ "content": "Where is my order 1042?" }'

The API returns 202 with the run. A session runs one message at a time; sending another while a run is active returns 409. Retry a lost request with the same Idempotency-Key to get the same run back.

Open the session’s event stream to follow the run:

GET /v1/agents/{agent_id}/sessions/{session_id}/events
Terminal window
curl -N "https://api.dexto.ai/v1/agents/$AGENT_ID/sessions/$SESSION_ID/events" \
-H "Authorization: Bearer $DEXTO_API_KEY"

Each server-sent event is named by its type:

EventMeaning
run.startedThe agent started working on a message, with its trigger.
message.deltaMore of the reply’s text.
message.completedThe full text of one reply.
tool.startedThe agent called a tool, with its arguments.
tool.completedThe tool finished, with its status, error, output and cost.
approval.requestedA tool is waiting for your approval.
approval.resolvedThe approval was decided.
run.completedThe run finished, with its stop_reason and usage.
run.failedThe run stopped, with an error code and message, and its usage.
run.cancelledThe run was cancelled, with its usage.

Every event has an id. If the connection drops, reconnect with the last id in the Last-Event-ID header to continue where you left off. GET …/messages returns the conversation so far.

tool.started carries the tool’s input as arguments, and tool.completed reports how the call went:

{
"type": "tool.completed",
"run_id": "3f0c…",
"tool_call_id": "call_8d2…",
"name": "orders_lookup_order",
"status": "success",
"error": null,
"output": { "order_id": "1042", "status": "shipped" },
"output_truncated": false,
"usage": { "credits": 0.00031 }
}

status is success, error, rejected (you rejected its approval) or cancelled (the run was cancelled during the call). When the call did not succeed, error has the tool’s code (such as not_found, insufficient_credits or the generic tool_error), a message and whether a retry may succeed (retryable). A result whose error is null counts as a success.

output is the tool’s result and arguments its input. Both have recognised secrets, such as API keys, replaced with [REDACTED]. When either is larger than 8 KiB of JSON, it becomes a string holding the start of that JSON, and output_truncated or arguments_truncated is true. Both are also null and marked truncated when a replayed event lost them to the stream’s replay limits.

usage.credits is what the call was charged by the time the event was sent: its searches, fetches, Function calls and media generations. It is null when nothing was charged by then, for example a free call, or a media generation that settles later. Sandbox compute is charged to the sandbox and file storage to the project, never to a call.

The run’s last event, and the run itself, carry stop_reason beside status:

stop_reasonStatusWhy the run stopped
completedcompletedThe agent finished its reply.
cancelledcancelledYou cancelled the run.
spend_limit_reachedfailedA spend limit on the agent, project or key was reached.
insufficient_creditsfailedThe organization ran out of credits.
max_stepsfailedReserved for a step limit; runs have none yet.
errorfailedAny other failure.

Each also carries usage: the run’s model input_tokens and output_tokens and the credits charged so far for its model calls, searches, fetches, Function calls and media. Charges settle as work finishes, so a later read can be a little higher than the event. Read a run at any time:

GET /v1/agents/{agent_id}/sessions/{session_id}/runs/{run_id}

The run returned by POST …/messages, POST …/cancel and a session’s active_run has the same fields; stop_reason is null until the run stops.

A run that an automation started, and its run.started event, carry trigger: { "type": "automation", "automation_id": "…", "firing_id": "…" }. Every other run has trigger: null, as does a run whose automation was deleted. active_run leaves it out.

The event stream is live. To read a finished run, or one still going, page through its steps:

GET /v1/agents/{agent_id}/sessions/{session_id}/runs/{run_id}/steps
Terminal window
curl "https://api.dexto.ai/v1/agents/$AGENT_ID/sessions/$SESSION_ID/runs/$RUN_ID/steps?limit=50" \
-H "Authorization: Bearer $DEXTO_API_KEY"

Steps come oldest first, with has_more and next_after. Pass next_after as after for the next page; limit is 1 to 100 and defaults to 50. A step is one of two kinds:

  • A model_turn is one model response: its model, its visible text, the tool_call_ids it asked for and its usage in tokens. A response that only called tools has empty text. Its status is running, completed, failed or cancelled. Reasoning, prompts and the provider’s own error text are never included.
  • A tool_call follows the model turn that made it. It has the name, arguments, output, error and usage that tool.started and tool.completed carry, with the same redaction and the same 8 KiB limit, and the same status values plus running and waiting_for_approval while the call is unfinished.
{
"object": "run_step",
"id": "call_8d2…",
"run_id": "3f0c…",
"type": "tool_call",
"name": "orders_refund_order",
"status": "success",
"arguments": { "order_id": "1042" },
"arguments_truncated": false,
"output": { "refunded": true },
"output_truncated": false,
"error": null,
"approval": {
"id": "7c1e…",
"status": "approved",
"requested_at": "2026-10-15T09:00:00.000Z",
"decided_at": "2026-10-15T09:00:42.000Z",
"expires_at": null
},
"usage": null,
"started_at": "2026-10-15T09:00:00.000Z",
"completed_at": "2026-10-15T09:00:43.000Z"
}

Approvals are not steps of their own. The call that waited for one holds it as approval, with its status (pending, approved, rejected or expired) and when it was requested and decided. null means the call needed none.

Steps stay readable as long as the session exists. A run can have many steps: to follow every page in code, iterate dexto.agents.sessions.runs.steps(agentId, sessionId, runId).

List a session’s runs, newest first, with the same paging. Each is the run GET …/runs/{run_id} returns:

GET /v1/agents/{agent_id}/sessions/{session_id}/runs

When a tool with require_approval is called, the run pauses and the stream sends approval.requested with the approval_id, tool_name and input. Decide it:

POST /v1/agents/{agent_id}/sessions/{session_id}/approvals/{approval_id}
Terminal window
curl -X POST "https://api.dexto.ai/v1/agents/$AGENT_ID/sessions/$SESSION_ID/approvals/$APPROVAL_ID" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "decision": "approve" }'

"reject" stops the tool, and the agent is told it was rejected. POST …/cancel stops the session’s active run.

Sessions are kept until you delete them. To honour a deletion request from one of your users, delete one session at once, or everything of a user or an agent in the background.

DELETE /v1/agents/{agent_id}/sessions/{session_id}

Deleting a session cancels its active run, ends its session sandbox and deletes its messages, runs, approvals and stored tool output. It returns the session’s id with deleted: true; afterwards the session is not found.

POST /v1/data_deletions
Terminal window
curl -X POST "https://api.dexto.ai/v1/data_deletions" \
-H "Authorization: Bearer $DEXTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "user": "customer_42" }'
  • With user, Dexto deletes every session labelled with that user in your key’s Cloud project, or only one agent’s when you also pass agent_id. It also deletes the automations whose sessions carry that label, since they would write more data about the person.
  • With only agent_id, the agent stops at once: new sessions and messages get 409 and its automations pause. Dexto then deletes its sessions, automations, firings and versions, and the agent is not found. Spend views still show its past spend under its name.

The API answers 202 with a data_deletion. Read it with GET /v1/data_deletions/{data_deletion_id} until status is completed (or failed); deleted counts the sessions and automations it removed. GET /v1/data_deletions lists them, newest first. The user label is never returned. A Cloud project can have at most 10 deletions queued or running.

While a user’s deletion is queued or running, creating a session for that user gets 409, so a completed deletion leaves none behind. If a deletion keeps failing, it ends failed with an error: what it deleted stays deleted, and an agent it was deleting stays archived. Request the same deletion again to finish it.

Usage and billing records are never deleted: credits charged for a deleted session still count in the agent’s and project’s spend. They keep the agent, project and key, lose the session and run, and never hold message content or the user label. The organization’s audit log records each deletion with the key that asked for it, its counts and a salted hash of the label. Database backups keep deleted rows until they age out of the backup window.

An Owner or Admin can set a Cloud project’s session retention in the Cloud app: Dexto then deletes each session that number of days (1 to 3650) after its last message or run, as DELETE would. A session with a run in progress is never deleted. With a retention set, sessions report expires_at; without one, expires_at is null and sessions are kept until you delete them. API keys can’t change retention.

Runs, and the sandboxes they use, are paid from your organization’s credits. Your plan’s allowance in the Dexto app doesn’t apply: it never pays for runs, and it doesn’t limit which model an agent uses. When your credits run out, the run fails with insufficient_credits. An Owner or Admin can set a monthly spend limit on the agent, its Cloud project or an API key; once one is reached, the run fails with spend_limit_reached until the next month or a higher limit.

Sites, Workers and Functions you build in Dexto include the Dexto Cloud SDK, which wraps every operation as agents. Deployed, they call it with their own DEXTO_API_KEY:

import { createCapabilitiesClient } from "@dexto-cloudflare/capabilities-client";
const dexto = createCapabilitiesClient({ apiKey: env.DEXTO_API_KEY });
const agent = await dexto.agents.create({
name: "Acme support",
instructions: "You help Acme customers with orders.",
tools: [{ type: "web_search" }],
});
const session = await dexto.agents.sessions.create(agent.id, {
user: "customer_42",
});
await dexto.agents.sessions.sendMessage(agent.id, session.id, {
content: "Where is my order 1042?",
});
for await (const { event } of dexto.agents.sessions.events(agent.id, session.id)) {
if (event.type === "message.delta") process.stdout.write(event.text);
if (event.type === "tool.completed" && event.status !== "success") console.warn(event.error);
if (event.type === "run.completed" || event.type === "run.failed") {
console.log(event.stop_reason, event.usage.credits);
break;
}
}

dexto.agents.sessions.getRun(agentId, sessionId, runId) reads a run with its stop_reason and usage. dexto.agents.sessions.runs.list(agentId, sessionId) lists a session’s runs, and dexto.agents.sessions.runs.steps(agentId, sessionId, runId) iterates a run’s steps across pages:

for await (const step of dexto.agents.sessions.runs.steps(agent.id, session.id, run.id)) {
if (step.type === "tool_call") console.log(step.name, step.status, step.approval?.status);
}

dexto.agents.sessions.delete(agentId, sessionId) deletes a session, and dexto.dataDeletions.create, get and list wrap /v1/data_deletions.

See /v1/openapi.json for every request and response schema, error and rate limit.

Ask Dexto to build an agent app and it makes a Site with a chat page and its own backend. The agent is defined in the app’s agent.json: its name, instructions, tools and the page’s title, colour and starter prompts. The backend creates the agent on first use and updates it when the file changes, holds the key, and gives each visitor’s browser its own sessions, so no key reaches the page. The page streams replies as Markdown with code and maths, shows each tool call, asks for approvals, and can stop a run.