Approvals API
Decide the runs waiting on a person from your own tooling: list open approvals and approve, deny, or approve with edits over HTTP.
An approval is a step that parks its run in needs_attention until someone
approves or denies it; the decision resumes the run from its checkpoint. Reads work with a public
key; the decision needs a secret key.
Endpoints
| Method + path | Purpose |
|---|---|
GET /approvals | The project's approvals, newest first. |
GET /approvals/{id} | One approval, with the proposed tool call and its context. |
POST /approvals/{id}/decision | Approve or deny an open approval; the parked run resumes. |
Listing
GET /approvals accepts:
| Param | Meaning | Default |
|---|---|---|
status | One of pending, escalated, approved, denied. pending and escalated are open, awaiting a decision. An unknown value returns 400. | all |
runId | Only one run's approvals. | all |
limit | Page size, 1-1000. A non-integer returns 400. | 100 |
A decided approval stays listable as its audit trail. Each approval is:
{
"id": "01JZR4A0...", "runId": "01JZR3Z9...",
"workflow": "support.refund", "app": "support", "step": "refund-gate",
"tool": "issue-refund",
"args": { "orderId": "A1", "amount": 4200, "currency": "usd" },
"risk": "high",
"policy": "tools.issue-refund -> require approval",
"summary": "Refund 4200 to A1 for a duplicate charge",
"escalatesTo": "#support-leads",
"status": "pending",
"requestedAt": "2026-07-01T10:00:00Z",
"expiresAt": "2026-07-01T10:30:00Z"
}| Field | Meaning |
|---|---|
id, runId | The approval and the run parked on it. |
workflow, app | Joined from the parked run. |
step | The step.approval name in the workflow. |
tool, args | The proposed action awaiting sign-off. |
risk | The declared risk level. |
policy, summary, context | Annotations for whoever reviews it. Absent when not set. |
escalatesTo | The escalation target once expiresAt passes. |
status | pending, escalated, approved, or denied. |
requestedAt, expiresAt | When it was requested, and when it escalates. expiresAt absent without a timeout. |
decidedAt, decidedBy, editedArgs | Set once decided: when, by whom, and the decider's edited arguments (approve-with-edits). Absent while open. |
Deciding
POST /approvals/{id}/decision applies the decision and resumes the run:
{ "status": "approved", "decidedBy": "ali", "args": { "orderId": "A1", "amount": 2100, "currency": "usd" } }status must be approved or denied. decidedBy labels the decision (it defaults to the calling
API key). args, when present, replaces the proposed tool arguments on the approved decision -
approve-with-edits; the workflow receives them as the effective decision.args. The response is the
decided approval.
import { createClient } from "@duraton/sdk/client";
const duraton = createClient({ url: process.env.DURATON_URL! });
const open = await duraton.approvals.list({ status: "pending" });
await duraton.approvals.decide(open[0].id, { status: "approved" });from duraton.client import ApprovalDecisionInput, AsyncDuratonClient, ListApprovalsOptions
async with AsyncDuratonClient() as dx:
open_approvals = await dx.approvals.list(ListApprovalsOptions(status="pending"))
await dx.approvals.decide(open_approvals[0].id, ApprovalDecisionInput(status="approved"))curl "$DURATON_URL/approvals?status=pending"
curl -X POST "$DURATON_URL/approvals/01JZR4A0.../decision" -d '{"status":"approved"}'
curl -X POST "$DURATON_URL/approvals/01JZR4A0.../decision" -d '{"status":"denied","decidedBy":"ali"}'Error codes
| Status | When |
|---|---|
400 | An unknown status filter; a decision whose status is not approved/denied; or invalid args JSON. |
404 | The approval id does not exist. |
409 | Deciding an approval that is already decided. |
Control API
Take control of a run in flight: cancel, pause, resume, replay it, or retry from a step - plain HTTP, with replay and retry forking a new run.
Evals API
Measure agent quality from your own tooling: score runs, author datasets of test cases, fan them through a workflow, and fork a finished run with one change.