DonutworkHelp Center
Donutwork documentation

AI Agents

Create, publish and operate persistent AI collaborators with bounded permissions, schedules, approvals and auditable runs.

AI Agents API

Use the AI Agents API to define a collaborator, assign one or more Tasks, publish an immutable configuration and start or monitor its work. Every resource is isolated to the organization associated with the bearer token.

Call these endpoints only from a trusted backend. Agent input, results, memory and action arguments can contain business data. Grant API keys only the scopes and Agent tools required by the integration.

All examples use API version 2026-02-01. Send JSON with Content-Type: application/json and authenticate with Authorization: Bearer YOUR_DONUTWORK_TOKEN.

Lifecycle at a glance

  1. Create an Agent in draft state.
  2. Add at least one Task and select the tools it can use.
  3. Run the safety check, then publish the Agent.
  4. Start runs manually through the API, or let published schedule and event triggers start them.
  5. Decide requested approvals and reconcile actions whose external outcome could not be confirmed.

Publishing creates an immutable release. Updating an Agent or Task changes only the draft until a new release is published. Use an Idempotency-Key header when creating a run; reusing the same key with a different payload returns 409 Conflict.

Agents

List Agents

GET
/2026-02-01/ai-agents/agents.json
Required permissionai_agents:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Agents visible to the authenticated organization.

{
  "elements": [
    {
      "id": "agent_123",
      "name": "Subscription Success",
      "goal": "Review subscription risks every morning",
      "status": "active",
      "approval_policy": "supervised",
      "current_release_version": 2
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Create an Agent

The Agent starts as a draft. If principal_id is omitted, Donutwork creates a company-scoped permission profile for it.

POST
/2026-02-01/ai-agents/agents.json
Required permissionai_agents:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "name": "Subscription Success",
  "goal": "Review subscription risks every morning and publish priorities",
  "approval_policy": "supervised",
  "inference_profile": "donut_managed_credits",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "max_steps": 8,
  "max_tool_calls": 6,
  "max_runs_per_day": 24
}
namestringRequired
Human-readable Agent name, up to 120 characters.
goalstringRequired
Business outcome assigned to the Agent, up to 8,000 characters.
instructionsstring
Boundaries, tone and operating guidance applied to every Task.
approval_policystring
supervised (default) or delegated.
inference_profilestring
One of the AI profiles enabled in company Agent settings.
assistant_idstring
Required when inference_profile is ai_gateway.
principal_idstring
Existing active company permission profile. Omit to create one automatically.
tool_namesstring[]
Tool keys returned by the tools endpoint.
max_stepsinteger
Maximum reasoning steps per run.
max_tool_callsinteger
Maximum tool calls per run.
max_runs_per_dayinteger
Daily run limit for this Agent.

Responses

Draft Agent created.

{
  "id": "agent_123",
  "name": "Subscription Success",
  "status": "draft",
  "approval_policy": "supervised"
}

A required field, profile, policy or limit is invalid.

{
  "error": "Invalid Agent configuration"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Subscription Success",
  "goal": "Review subscription risks every morning and publish priorities",
  "approval_policy": "supervised",
  "inference_profile": "donut_managed_credits",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "max_steps": 8,
  "max_tool_calls": 6,
  "max_runs_per_day": 24
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Subscription Success",
    "goal": "Review subscription risks every morning and publish priorities",
    "approval_policy": "supervised",
    "inference_profile": "donut_managed_credits",
    "tool_names": [
      "search_billing_records",
      "publish_artifact"
    ],
    "max_steps": 8,
    "max_tool_calls": 6,
    "max_runs_per_day": 24
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "name": "Subscription Success",
  "goal": "Review subscription risks every morning and publish priorities",
  "approval_policy": "supervised",
  "inference_profile": "donut_managed_credits",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "max_steps": 8,
  "max_tool_calls": 6,
  "max_runs_per_day": 24
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Get an Agent

The default API response includes Tasks, releases and memory metadata but excludes decrypted memory content.

GET
/2026-02-01/ai-agents/agents/{agentId}.json
Required permissionai_agents:read

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Responses

Agent configuration and related resources.

{
  "id": "agent_123",
  "name": "Subscription Success",
  "status": "active",
  "tasks": [
    {
      "id": "task_123",
      "name": "Morning review"
    }
  ],
  "releases": [
    {
      "id": "release_2",
      "version": 2,
      "status": "active"
    }
  ],
  "memory_usage": {
    "items": 3,
    "used_bytes": 640,
    "capacity_bytes": 24000
  }
}

Agent not found in the authenticated organization.

{
  "error": "Agent not found"
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Update an Agent draft

Only supplied fields are changed. Publish again before saved draft changes affect new runs.

PUT
/2026-02-01/ai-agents/agents/{agentId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Request Body

JSON
{
  "goal": "Review subscription risks and publish the top five priorities",
  "approval_policy": "delegated",
  "max_runs_per_day": 12
}
namestring
Updated Agent name.
goalstring
Updated business outcome.
instructionsstring
Updated operating guidance.
approval_policystring
supervised or delegated.
inference_profilestring
Enabled inference profile.
assistant_idstring
AI Gateway assistant for the ai_gateway profile.
principal_idstring
Active company permission profile.
tool_namesstring[]
Complete replacement list of Agent tool keys.
max_stepsinteger
Maximum reasoning steps per run.
max_tool_callsinteger
Maximum tool calls per run.
max_runs_per_dayinteger
Daily run limit for this Agent.

Responses

Agent draft updated.

{
  "id": "agent_123",
  "name": "Subscription Success",
  "approval_policy": "delegated",
  "draft_revision": 4
}

Agent not found.

{
  "error": "Agent not found"
}

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "goal": "Review subscription risks and publish the top five priorities",
  "approval_policy": "delegated",
  "max_runs_per_day": 12
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "goal": "Review subscription risks and publish the top five priorities",
    "approval_policy": "delegated",
    "max_runs_per_day": 12
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "goal": "Review subscription risks and publish the top five priorities",
  "approval_policy": "delegated",
  "max_runs_per_day": 12
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Delete an Agent

Deletion stops triggers and pending work while retaining published releases and run history according to the configured retention policy. Running or unresolved runs must be completed first.

DELETE
/2026-02-01/ai-agents/agents/{agentId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Responses

Agent and its current Tasks removed from the workspace.

{
  "deleted": true,
  "agent_id": "agent_123"
}

The Agent still has running or unresolved work.

{
  "error": "Complete active Agent runs before deleting it"
}

Request examples

curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Pause or reactivate an Agent

Pausing prevents new work and cancels queued runs and pending approvals. Reactivation requires a compatible published release and an active permission profile.

POST
/2026-02-01/ai-agents/agents/{agentId}/status.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Request Body

JSON
{
  "status": "paused"
}
statusstringRequired
active or paused.

Responses

Agent status changed.

{
  "id": "agent_123",
  "status": "paused"
}

The Agent cannot be activated with its current release or permission profile.

{
  "error": "Publish the Agent before activating it"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/status.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "status": "paused"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/status.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "status": "paused"
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/status.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "status": "paused"
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Tasks

Create a Task

A Task defines one expected outcome and one start condition. For schedule, include a schedule object. For event, include event_name.

POST
/2026-02-01/ai-agents/agents/{agentId}/tasks.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Owning Agent identifier.

Request Body

JSON
{
  "name": "Morning review",
  "objective": "Publish the five subscriptions that need attention",
  "trigger_type": "schedule",
  "schedule": {
    "frequency": "daily",
    "interval": 1,
    "time": "08:00",
    "timezone": "Europe/Rome"
  },
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "status": "active",
  "overlap_policy": "skip",
  "on_error": "stop",
  "max_runs_per_day": 1
}
namestringRequired
Task name, unique within the Agent.
objectivestringRequired
Result the Task must produce.
instructionsstring
Guidance specific to this Task.
trigger_typestring
manual (default), schedule or event.
scheduleobject
Frequency, interval, local time and IANA timezone for a scheduled Task.
event_namestring
Business event name for an event-triggered Task.
tool_namesstring[]
Task tool subset. A Task cannot expand the Agent tool set.
email_deliveryobject
Optional transactional email template and content-variable configuration.
depends_onstring[]
Task identifiers that must finish before this Task starts.
dependency_policystring
all_succeeded, all_completed or any_succeeded.
overlap_policystring
skip or queue when another run is active.
on_errorstring
stop or continue dependent Tasks after failure.
max_runs_per_dayinteger
Daily run limit for this Task.

Responses

Task saved in the Agent draft.

{
  "id": "task_123",
  "agent_id": "agent_123",
  "name": "Morning review",
  "status": "active",
  "trigger_type": "schedule"
}

Task fields, schedule or dependency graph are invalid.

{
  "error": "Invalid Task configuration"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Morning review",
  "objective": "Publish the five subscriptions that need attention",
  "trigger_type": "schedule",
  "schedule": {
    "frequency": "daily",
    "interval": 1,
    "time": "08:00",
    "timezone": "Europe/Rome"
  },
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "status": "active",
  "overlap_policy": "skip",
  "on_error": "stop",
  "max_runs_per_day": 1
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Morning review",
    "objective": "Publish the five subscriptions that need attention",
    "trigger_type": "schedule",
    "schedule": {
      "frequency": "daily",
      "interval": 1,
      "time": "08:00",
      "timezone": "Europe/Rome"
    },
    "tool_names": [
      "search_billing_records",
      "publish_artifact"
    ],
    "status": "active",
    "overlap_policy": "skip",
    "on_error": "stop",
    "max_runs_per_day": 1
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "name": "Morning review",
  "objective": "Publish the five subscriptions that need attention",
  "trigger_type": "schedule",
  "schedule": {
    "frequency": "daily",
    "interval": 1,
    "time": "08:00",
    "timezone": "Europe/Rome"
  },
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ],
  "status": "active",
  "overlap_policy": "skip",
  "on_error": "stop",
  "max_runs_per_day": 1
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Update a Task

The request replaces the saved Task configuration. Publish the Agent again before changes affect new runs.

PUT
/2026-02-01/ai-agents/agents/{agentId}/tasks/{taskId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Owning Agent identifier.
taskIdstring· pathRequired
Task identifier.

Request Body

JSON
{
  "name": "Morning review",
  "objective": "Publish the top five priorities",
  "trigger_type": "manual",
  "status": "active",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ]
}

Responses

Task draft updated.

{
  "id": "task_123",
  "agent_id": "agent_123",
  "trigger_type": "manual"
}

Agent or Task not found.

{
  "error": "Task not found"
}

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Morning review",
  "objective": "Publish the top five priorities",
  "trigger_type": "manual",
  "status": "active",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ]
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Morning review",
    "objective": "Publish the top five priorities",
    "trigger_type": "manual",
    "status": "active",
    "tool_names": [
      "search_billing_records",
      "publish_artifact"
    ]
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "name": "Morning review",
  "objective": "Publish the top five priorities",
  "trigger_type": "manual",
  "status": "active",
  "tool_names": [
    "search_billing_records",
    "publish_artifact"
  ]
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Delete a Task

DELETE
/2026-02-01/ai-agents/agents/{agentId}/tasks/{taskId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Owning Agent identifier.
taskIdstring· pathRequired
Task identifier.

Responses

Task removed and references from current draft dependencies cleaned up.

{
  "deleted": true,
  "agent_id": "agent_123",
  "task_id": "task_123"
}

The Task still has running or unresolved work.

{
  "error": "Complete active Task runs before deleting it"
}

Request examples

curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/tasks/TASKID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Releases

Check an Agent configuration

Run this check immediately before publishing. Errors block publication; warnings describe choices that deserve review.

POST
/2026-02-01/ai-agents/agents/{agentId}/safety-check.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Responses

Configuration check completed.

{
  "passed": true,
  "errors": [],
  "warnings": [
    {
      "code": "delegated_actions",
      "message": "Delegated actions can proceed without human approval"
    }
  ]
}

Agent not found.

{
  "error": "Agent not found"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/safety-check.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/safety-check.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/safety-check.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Publish an Agent

Publication snapshots the Agent, its current Tasks, permissions, selected inference profile and exposed tool contracts. A successful publication activates the Agent and its published triggers.

POST
/2026-02-01/ai-agents/agents/{agentId}/releases.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Responses

New immutable release published and activated.

{
  "id": "release_2",
  "agent_id": "agent_123",
  "version": 2,
  "status": "active"
}

The safety check found blocking configuration errors.

{
  "error": "The Agent safety check failed"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Restore a published release

Restoring changes which release starts future runs. Existing runs remain bound to their original release.

POST
/2026-02-01/ai-agents/agents/{agentId}/releases/{releaseId}/activate.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.
releaseIdstring· pathRequired
Published release identifier.

Responses

Published release activated.

{
  "id": "release_1",
  "agent_id": "agent_123",
  "version": 1,
  "status": "active"
}

The release is incompatible with the current tools or references a deleted Task.

{
  "error": "Release cannot be restored"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID/activate.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID/activate.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID/activate.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Delete a published release

Deleting a non-current version removes it from the versions available for restoration and prevents it from starting new activities. Existing run history remains bound to its immutable snapshot. Restore or publish another version before deleting the current one.

DELETE
/2026-02-01/ai-agents/agents/{agentId}/releases/{releaseId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.
releaseIdstring· pathRequired
Published release identifier.

Responses

Published version removed from the available release history.

{
  "deleted": true,
  "agent_id": "agent_123",
  "release_id": "release_1",
  "version": 1
}

Agent or published version not found.

{
  "error": "Agent release not found"
}

The selected version is current or changed concurrently.

{
  "error": "The current published version cannot be deleted"
}

Request examples

curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID.json', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/releases/RELEASEID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Runs

List runs

Use the optional agentId query parameter to restrict results to one Agent.

GET
/2026-02-01/ai-agents/runs.json
Required permissionai_agent_runs:read

Path, query, and header parameters

agentIdstring· query
Return runs for this Agent only.

Responses

Recent runs in the authenticated organization.

{
  "elements": [
    {
      "id": "run_123",
      "agent_id": "agent_123",
      "task_id": "task_123",
      "status": "completed",
      "source": "api",
      "created_at": "2026-09-11T08:00:00+00:00"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/runs.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Get a run

This detailed response can include decrypted run input, working steps, output and artifacts. Store and display it according to your own data-access policy.

GET
/2026-02-01/ai-agents/runs/{runId}.json
Required permissionai_agent_runs:read

Path, query, and header parameters

runIdstring· pathRequired
Run identifier.

Responses

Detailed run state and results.

{
  "id": "run_123",
  "status": "completed",
  "input": {
    "report_date": "2026-09-11"
  },
  "output": {
    "answer": "Five subscriptions need attention."
  },
  "steps": [
    {
      "sequence": 1,
      "status": "completed",
      "summary": "Reviewed current subscription records"
    }
  ],
  "artifacts": []
}

Run not found.

{
  "error": "Run not found"
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Start a run

If task_id is omitted, the Agent must have exactly one active Task in its published release. input must be a JSON object smaller than 128 KB.

POST
/2026-02-01/ai-agents/agents/{agentId}/runs.json
Required permissionai_agent_runs:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Request Body

JSON
{
  "task_id": "task_123",
  "input": {
    "report_date": "2026-09-11"
  },
  "idempotency_key": "subscription-review-2026-09-11"
}
task_idstring
Task to run. Optional only when the published Agent has one active Task.
inputobject
Business input made available to the Agent for this run.
idempotency_keystring
Stable request key. It may also be supplied in the Idempotency-Key header.

Responses

Run created and queued.

{
  "id": "run_123",
  "agent_id": "agent_123",
  "task_id": "task_123",
  "status": "queued",
  "source": "api"
}

The Agent is not active, its release must be updated, a limit was reached, or the idempotency key conflicts.

{
  "error": "Unable to create the Agent run"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/runs.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "task_id": "task_123",
  "input": {
    "report_date": "2026-09-11"
  },
  "idempotency_key": "subscription-review-2026-09-11"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/runs.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "task_id": "task_123",
    "input": {
      "report_date": "2026-09-11"
    },
    "idempotency_key": "subscription-review-2026-09-11"
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/runs.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "task_id": "task_123",
  "input": {
    "report_date": "2026-09-11"
  },
  "idempotency_key": "subscription-review-2026-09-11"
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Cancel a run

Queued, running and approval-waiting runs can be cancelled. A completed or otherwise terminal run cannot be changed.

POST
/2026-02-01/ai-agents/runs/{runId}/cancel.json
Required permissionai_agent_runs:write

Path, query, and header parameters

runIdstring· pathRequired
Run identifier.

Responses

Run cancelled.

{
  "id": "run_123",
  "status": "cancelled"
}

Run is already terminal.

{
  "error": "The Agent run cannot be cancelled"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/cancel.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/cancel.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/cancel.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Reconcile an action outcome

Use this endpoint only when a run is in reconciliation_required. First verify the real outcome in the destination system. Donutwork does not execute the action again.

POST
/2026-02-01/ai-agents/runs/{runId}/steps/{stepId}/reconcile.json
Required permissionai_agent_runs:write

Path, query, and header parameters

runIdstring· pathRequired
Run identifier.
stepIdstring· pathRequired
Unresolved action step identifier.

Request Body

JSON
{
  "outcome": "succeeded",
  "result": {
    "external_reference": "crm_456"
  },
  "note": "Verified in the destination audit log"
}
outcomestringRequired
succeeded to resume from the checkpoint, or failed to close the run.
resultobject
Verified receipt or identifiers returned by the destination system.
notestringRequired
How the operator verified the external outcome.

Responses

Verified outcome recorded.

{
  "id": "run_123",
  "status": "queued"
}

The step or run no longer requires reconciliation.

{
  "error": "This Agent step no longer requires reconciliation"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/steps/STEPID/reconcile.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "outcome": "succeeded",
  "result": {
    "external_reference": "crm_456"
  },
  "note": "Verified in the destination audit log"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/steps/STEPID/reconcile.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "outcome": "succeeded",
    "result": {
      "external_reference": "crm_456"
    },
    "note": "Verified in the destination audit log"
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/runs/RUNID/steps/STEPID/reconcile.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "outcome": "succeeded",
  "result": {
    "external_reference": "crm_456"
  },
  "note": "Verified in the destination audit log"
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Approvals

List approvals

The default status is pending. Use all to retrieve pending and decided approvals.

GET
/2026-02-01/ai-agents/approvals.json
Required permissionai_agent_approvals:read

Path, query, and header parameters

statusstring· query
pending (default), approved, rejected, cancelled or all.

Responses

Approval requests matching the selected status.

{
  "elements": [
    {
      "id": "approval_123",
      "agent_id": "agent_123",
      "run_id": "run_123",
      "status": "pending",
      "summary": "Start the customer follow-up workflow",
      "arguments": {
        "workflow_id": "workflow_123"
      }
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Decide an approval

An approval is one-shot and applies only to the exact action and arguments in the request. Later sensitive actions require a separate decision.

POST
/2026-02-01/ai-agents/approvals/{approvalId}/decision.json
Required permissionai_agent_approvals:write

Path, query, and header parameters

approvalIdstring· pathRequired
Pending approval identifier.

Request Body

JSON
{
  "decision": "approved",
  "note": "Reviewed against the customer follow-up policy"
}
decisionstringRequired
approved or rejected.
notestring
Optional reason or review note.

Responses

Decision recorded and the run made eligible to continue when approved.

{
  "id": "approval_123",
  "status": "approved",
  "run_id": "run_123"
}

Approval is no longer pending or the run is no longer waiting for it.

{
  "error": "The approval is no longer pending"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals/APPROVALID/decision.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "decision": "approved",
  "note": "Reviewed against the customer follow-up policy"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals/APPROVALID/decision.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "decision": "approved",
    "note": "Reviewed against the customer follow-up policy"
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/approvals/APPROVALID/decision.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "decision": "approved",
  "note": "Reviewed against the customer follow-up policy"
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Permission profiles

An Agent permission profile is company-scoped and determines which Donutwork business permissions the Agent may exercise. A Task can further restrict tools but cannot expand this profile.

List permission profiles

GET
/2026-02-01/ai-agents/principals.json
Required permissionai_agents:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Agent permission profiles in the authenticated organization.

{
  "elements": [
    {
      "id": "principal_123",
      "name": "Subscription Success access",
      "status": "active",
      "grants": [
        "recurring_payments.view"
      ]
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Create a permission profile

POST
/2026-02-01/ai-agents/principals.json
Required permissionai_agents:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view",
    "email_gateway.manage"
  ]
}
namestringRequired
Human-readable profile name.
slugstring
Optional stable slug derived from the name when omitted.
grantsstring[]
Backoffice permission keys required by the selected Agent tools.

Responses

Active permission profile created.

{
  "id": "principal_123",
  "name": "Subscription Success access",
  "status": "active",
  "grants": [
    "recurring_payments.view",
    "email_gateway.manage"
  ]
}

A profile with the same name already exists.

{
  "error": "A service principal with this name already exists"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view",
    "email_gateway.manage"
  ]
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Subscription Success access",
    "grants": [
      "recurring_payments.view",
      "email_gateway.manage"
    ]
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view",
    "email_gateway.manage"
  ]
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Update or revoke a permission profile

Revoking a profile pauses Agents that use it. Reactivation requires a new active profile and a valid published configuration.

PUT
/2026-02-01/ai-agents/principals/{principalId}.json
Required permissionai_agents:write

Path, query, and header parameters

principalIdstring· pathRequired
Permission profile identifier.

Request Body

JSON
{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view"
  ],
  "status": "active"
}
namestring
Updated display name.
grantsstring[]
Complete replacement permission list.
statusstring
active or revoked.

Responses

Permission profile updated.

{
  "id": "principal_123",
  "status": "active",
  "grants": [
    "recurring_payments.view"
  ]
}

Permission profile not found.

{
  "error": "Service principal not found"
}

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/ai-agents/principals/PRINCIPALID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view"
  ],
  "status": "active"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals/PRINCIPALID.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Subscription Success access",
    "grants": [
      "recurring_payments.view"
    ],
    "status": "active"
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/principals/PRINCIPALID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "name": "Subscription Success access",
  "grants": [
    "recurring_payments.view"
  ],
  "status": "active"
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Company settings and tools

Get Agent settings

GET
/2026-02-01/ai-agents/settings.json
Required permissionai_agents:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Company Agent profiles, retention and volume limits.

{
  "enabled_profiles": [
    "ai_gateway",
    "donut_managed_credits"
  ],
  "default_profile": "donut_managed_credits",
  "retention_days": 30,
  "max_concurrent_runs": 3,
  "monthly_run_limit": 1000
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Update Agent settings

At least one environment-enabled inference profile must remain enabled for the company. Agents never switch profiles automatically during a run.

PUT
/2026-02-01/ai-agents/settings.json
Required permissionai_agents:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "enabled_profiles": [
    "donut_managed_credits"
  ],
  "default_profile": "donut_managed_credits",
  "retention_days": 14,
  "max_concurrent_runs": 5,
  "monthly_run_limit": 2500
}
enabled_profilesstring[]Required
Allowed subset of environment-enabled inference profiles.
default_profilestringRequired
Profile preselected for new Agents.
retention_daysinteger
Operational payload retention from 1 to 30 days.
max_concurrent_runsinteger
Maximum simultaneous runs for the organization.
monthly_run_limitinteger
Monthly organization run limit.

Responses

Company Agent settings updated.

{
  "enabled_profiles": [
    "donut_managed_credits"
  ],
  "default_profile": "donut_managed_credits",
  "retention_days": 14,
  "max_concurrent_runs": 5,
  "monthly_run_limit": 2500
}

No profile is enabled or the default profile is not in the enabled list.

{
  "error": "The default profile must be enabled for the company"
}

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "enabled_profiles": [
    "donut_managed_credits"
  ],
  "default_profile": "donut_managed_credits",
  "retention_days": 14,
  "max_concurrent_runs": 5,
  "monthly_run_limit": 2500
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "enabled_profiles": [
      "donut_managed_credits"
    ],
    "default_profile": "donut_managed_credits",
    "retention_days": 14,
    "max_concurrent_runs": 5,
    "monthly_run_limit": 2500
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/settings.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "enabled_profiles": [
    "donut_managed_credits"
  ],
  "default_profile": "donut_managed_credits",
  "retention_days": 14,
  "max_concurrent_runs": 5,
  "monthly_run_limit": 2500
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

List Agent tools

Tool definitions include their JSON input schema, required business permissions, risk classification and whether the action can be delegated. Treat the returned catalog as authoritative for the current environment.

GET
/2026-02-01/ai-agents/tools.json
Required permissionai_agents:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Current Agent tool catalog.

{
  "elements": [
    {
      "name": "publish_artifact",
      "description": "Publish a named result for this run",
      "parameters": {
        "type": "object"
      },
      "permissions": [],
      "risk": "R0",
      "read_only": false,
      "delegatable": true
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-agents/tools.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/tools.json', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/tools.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Pin Agent memory

Pinned memory is managed by your organization and is made available to every Task before each decision. Pinning the same key replaces its content. Content is encrypted at rest and must fit within the Agent memory capacity.

POST
/2026-02-01/ai-agents/agents/{agentId}/memory.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.

Request Body

JSON
{
  "key": "brand_tone",
  "content": "Use concise, direct language in every customer-facing draft."
}
keystringRequired
Stable memory key, up to 80 characters.
contentstringRequired
Pinned information, up to 16,000 characters within the total Agent capacity.

Responses

Pinned memory saved or replaced.

{
  "id": "memory_123",
  "agent_id": "agent_123",
  "key": "brand_tone",
  "kind": "pinned"
}

The key, content or resulting total memory size is invalid.

{
  "error": "Agent memory capacity exceeded"
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "key": "brand_tone",
  "content": "Use concise, direct language in every customer-facing draft."
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "key": "brand_tone",
    "content": "Use concise, direct language in every customer-facing draft."
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "key": "brand_tone",
  "content": "Use concise, direct language in every customer-facing draft."
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

Delete Agent memory

Both pinned information and temporary information learned by the Agent can be removed. Once deleted, the item is no longer supplied to any Task in future activities.

DELETE
/2026-02-01/ai-agents/agents/{agentId}/memory/{memoryId}.json
Required permissionai_agents:write

Path, query, and header parameters

agentIdstring· pathRequired
Agent identifier.
memoryIdstring· pathRequired
Memory item identifier.

Responses

Memory item deleted.

{
  "deleted": true,
  "agent_id": "agent_123",
  "memory_id": "memory_123"
}

Agent or memory item not found.

{
  "error": "Memory not found"
}

Request examples

curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory/MEMORYID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory/MEMORYID.json', {
  method: 'DELETE',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
  },
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/ai-agents/agents/AGENTID/memory/MEMORYID.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
    ],
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

On this page