DonutworkHelp Center
Documentazione Donutwork

AI Runtime

Govern AI systems and agents, record executions, authorize tool actions, resolve approvals, and export technical evidence.

AI Runtime API

AI Runtime is a vendor-neutral governance layer for observing AI executions, authorizing agent actions, requesting human oversight, and exporting technical evidence. It does not proxy model traffic or execute tools. Your application performs the model or tool call after applying the returned decision.

Availability

AI Runtime is a non-production surface. In a production deployment its REST routes may be unavailable and resolve as not found. Confirm enablement with Donutwork before building a dependency on it.

Lifecycle and safety model

  1. Create an AI system and register a versioned agent identity.
  2. Start an execution and retain its ID.
  3. Record LLM traces without raw prompt or response content.
  4. Call authorize before a governed tool action.
  5. Continue only on ALLOW, or after a REQUIRE_APPROVAL approval becomes approved; never continue on DENY.
  6. Record the tool outcome and deterministic evaluations.
  7. Finish the execution and export evidence.

Authorization is fail-closed: no matching policy and evaluator failures both return DENY. Executions transition only from running to completed, failed, or cancelled. Use Idempotency-Key on execution, trace, authorization, and evaluation writes; the same key must never be reused with a different payload.

AI systems

List systems

GET
/2026-02-01/ai-runtime/systems.json
Required permissionai_runtime:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Systems in the authenticated company.

{
  "elements": [
    {
      "id": "SYSTEM_ID",
      "slug": "finance-agent",
      "name": "Finance agent",
      "purpose": "Review payment proposals",
      "environment": "staging",
      "status": "active"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems.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-runtime/systems.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 system

POST
/2026-02-01/ai-runtime/systems.json
Required permissionai_runtime:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "name": "Finance agent",
  "slug": "finance-agent",
  "purpose": "Review payment proposals before execution",
  "environment": "staging",
  "owner_id": "team-finance",
  "risk_metadata": {
    "impact": "medium"
  }
}
namestringRequired
Display name, up to 120 characters.
purposestringRequired
Specific governed use case, up to 1,000 characters.
slugstring
Stable company-unique slug; derived from name when omitted.
environmentenum
development, test, staging, or production. Default: production.
statusenum
active or inactive. Default: active.
owner_idstring
Your stable owner or team reference.
risk_metadataobject
Sanitized scalar risk metadata; never include secrets or raw content.
metadataobject
Sanitized integration metadata.

Responses

System registered.

{
  "id": "SYSTEM_ID",
  "slug": "finance-agent",
  "status": "active"
}

The slug already belongs to another system.

{
  "error": "An AI System with this slug already exists."
}

A required field or enum is invalid.

{
  "error": "purpose is required and must be at most 1000 characters."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Finance agent",
  "slug": "finance-agent",
  "purpose": "Review payment proposals before execution",
  "environment": "staging",
  "owner_id": "team-finance",
  "risk_metadata": {
    "impact": "medium"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Finance agent",
    "slug": "finance-agent",
    "purpose": "Review payment proposals before execution",
    "environment": "staging",
    "owner_id": "team-finance",
    "risk_metadata": {
      "impact": "medium"
    }
  }),
});
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-runtime/systems.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": "Finance agent",
  "slug": "finance-agent",
  "purpose": "Review payment proposals before execution",
  "environment": "staging",
  "owner_id": "team-finance",
  "risk_metadata": {
    "impact": "medium"
  }
}',
]);
$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.

Retrieve a system

GET
/2026-02-01/ai-runtime/systems/{systemId}.json
Required permissionai_runtime:read

Path, query, and header parameters

systemIdstring· pathRequired
Company-scoped AI system ID.

Responses

System record.

{
  "id": "SYSTEM_ID",
  "name": "Finance agent",
  "status": "active"
}

No system is visible in this company.

{
  "error": "AI System not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems/SYSTEMID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/systems/SYSTEMID.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-runtime/systems/SYSTEMID.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.

Agent identities

Agent identities are versioned records, not credentials. A revoked identity is terminal; register a new version instead of reactivating it.

List agent identities

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

Path, query, and header parameters

systemIdstring· query
Restrict results to one system owned by the authenticated company.

Responses

Agent identities, newest versions first per name.

{
  "elements": [
    {
      "id": "AGENT_ID",
      "system_id": "SYSTEM_ID",
      "name": "finance-agent",
      "version": "1.0.0",
      "status": "active"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/agents.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/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-runtime/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.

Register an agent identity

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

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "system_id": "SYSTEM_ID",
  "name": "finance-agent",
  "version": "1.0.0",
  "environment": "staging",
  "capabilities": [
    "payment.review",
    "payment.propose"
  ]
}
system_idstringRequired
Active AI system that owns this identity.
namestringRequired
Stable agent name, up to 120 characters.
versionstringRequired
Your immutable release or configuration version, up to 80 characters.
environmentenum
development, test, staging, or production; defaults to the system environment.
capabilitiesstring[]
Up to 100 declared capability names.
statusenum
active, inactive, or revoked. Default: active.
metadataobject
Sanitized integration metadata.

Responses

Agent version registered.

{
  "id": "AGENT_ID",
  "system_id": "SYSTEM_ID",
  "name": "finance-agent",
  "version": "1.0.0",
  "status": "active"
}

The system is inactive or this name/version already exists.

{
  "error": "This Agent Identity version is already registered."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/agents.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "system_id": "SYSTEM_ID",
  "name": "finance-agent",
  "version": "1.0.0",
  "environment": "staging",
  "capabilities": [
    "payment.review",
    "payment.propose"
  ]
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/agents.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "system_id": "SYSTEM_ID",
    "name": "finance-agent",
    "version": "1.0.0",
    "environment": "staging",
    "capabilities": [
      "payment.review",
      "payment.propose"
    ]
  }),
});
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-runtime/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 => '{
  "system_id": "SYSTEM_ID",
  "name": "finance-agent",
  "version": "1.0.0",
  "environment": "staging",
  "capabilities": [
    "payment.review",
    "payment.propose"
  ]
}',
]);
$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.

Retrieve an agent identity

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

Path, query, and header parameters

agentIdstring· pathRequired
Company-scoped Agent Identity ID.

Responses

Agent identity record.

{
  "id": "AGENT_ID",
  "system_id": "SYSTEM_ID",
  "status": "active"
}

Agent identity not found in this company.

{
  "error": "Agent Identity not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/agents/AGENTID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/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-runtime/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.

Executions and traces

List executions

GET
/2026-02-01/ai-runtime/executions.json
Required permissionai_runtime:read

Path, query, and header parameters

systemIdstring· query
Restrict results to one company-owned AI system.

Responses

Executions ordered newest first.

{
  "elements": [
    {
      "id": "EXECUTION_ID",
      "system_id": "SYSTEM_ID",
      "agent_id": "AGENT_ID",
      "status": "running"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions.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-runtime/executions.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 an execution

POST
/2026-02-01/ai-runtime/executions.json
Required permissionai_runtime_executions:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "system_id": "SYSTEM_ID",
  "agent_id": "AGENT_ID",
  "metadata": {
    "correlation_id": "order-8472"
  }
}
system_idstringRequired
Active company-owned AI system.
agent_idstring
Active Agent Identity belonging to the same system. When present, actor type is ai_agent.
actor_typeenum
human or api_client when agent_id is absent. Default: api_client.
actor_idstring
Your actor reference when agent_id is absent.
metadataobject
Sanitized correlation metadata; raw prompts and credentials are discarded.

Responses

Execution opened in running state.

{
  "id": "EXECUTION_ID",
  "system_id": "SYSTEM_ID",
  "agent_id": "AGENT_ID",
  "status": "running",
  "started_at": "2026-09-18T10:00:00+00:00"
}

System or agent is inactive, or an idempotency key was reused with different content.

{
  "error": "The Agent Identity is not active for this AI System."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "system_id": "SYSTEM_ID",
  "agent_id": "AGENT_ID",
  "metadata": {
    "correlation_id": "order-8472"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "system_id": "SYSTEM_ID",
    "agent_id": "AGENT_ID",
    "metadata": {
      "correlation_id": "order-8472"
    }
  }),
});
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-runtime/executions.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 => '{
  "system_id": "SYSTEM_ID",
  "agent_id": "AGENT_ID",
  "metadata": {
    "correlation_id": "order-8472"
  }
}',
]);
$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.

Retrieve an execution

GET
/2026-02-01/ai-runtime/executions/{executionId}.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· pathRequired
Company-scoped execution ID.

Responses

Execution state and sanitized metadata.

{
  "id": "EXECUTION_ID",
  "status": "running",
  "started_at": "2026-09-18T10:00:00+00:00"
}

Execution not found in this company.

{
  "error": "AI Execution not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID.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-runtime/executions/EXECUTIONID.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.

Finish an execution

POST
/2026-02-01/ai-runtime/executions/{executionId}/finish.json
Required permissionai_runtime_executions:write

Path, query, and header parameters

executionIdstring· pathRequired
Running execution to close.

Request Body

JSON
{
  "status": "completed",
  "metadata": {
    "outcome": "proposal_created"
  }
}
statusenum
completed, failed, or cancelled. Default: completed.
error_codestring
Sanitized code for failed executions; do not send provider diagnostics.
metadataobject
Sanitized outcome metadata.

Responses

Execution closed.

{
  "id": "EXECUTION_ID",
  "status": "completed",
  "completed_at": "2026-09-18T10:00:08+00:00"
}

The execution has already left running state.

{
  "error": "Execution cannot transition from completed."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/finish.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "status": "completed",
  "metadata": {
    "outcome": "proposal_created"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/finish.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "status": "completed",
    "metadata": {
      "outcome": "proposal_created"
    }
  }),
});
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-runtime/executions/EXECUTIONID/finish.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": "completed",
  "metadata": {
    "outcome": "proposal_created"
  }
}',
]);
$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.

Record an LLM trace

POST
/2026-02-01/ai-runtime/executions/{executionId}/llm-traces.json
Required permissionai_runtime_executions:write

Path, query, and header parameters

executionIdstring· pathRequired
Running execution that observed the model call.

Request Body

JSON
{
  "provider": "openai",
  "model": "gpt-example",
  "model_version": "2026-09",
  "model_type": "generation",
  "status": "completed",
  "latency_ms": 842,
  "usage": {
    "input_tokens": 320,
    "output_tokens": 84
  },
  "request_hash": "LOWERCASE_SHA256",
  "response_hash": "LOWERCASE_SHA256"
}
providerstringRequired
Provider identifier.
modelstringRequired
Provider model name, up to 200 characters.
model_versionstring
Observed provider or deployment version.
model_typeenum
generation, embedding, multimodal, or other.
statusenum
completed or failed. Default: completed.
requested_at / responded_atISO 8601
Provider request boundaries; response cannot precede request.
latency_msinteger
Non-negative observed latency.
usageobject
Sanitized token usage counters.
request_hash / response_hashSHA-256
Content hashes for correlation without retaining content.
error_codestring
Sanitized failure code when status is failed.

Responses

Trace stored and model inventory updated.

{
  "id": "TRACE_ID",
  "kind": "llm",
  "model_id": "MODEL_ID",
  "status": "completed",
  "audit_sequence": 2
}

Execution is closed or idempotency conflicts.

{
  "error": "The AI Execution is already completed."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/llm-traces.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "provider": "openai",
  "model": "gpt-example",
  "model_version": "2026-09",
  "model_type": "generation",
  "status": "completed",
  "latency_ms": 842,
  "usage": {
    "input_tokens": 320,
    "output_tokens": 84
  },
  "request_hash": "LOWERCASE_SHA256",
  "response_hash": "LOWERCASE_SHA256"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/llm-traces.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "provider": "openai",
    "model": "gpt-example",
    "model_version": "2026-09",
    "model_type": "generation",
    "status": "completed",
    "latency_ms": 842,
    "usage": {
      "input_tokens": 320,
      "output_tokens": 84
    },
    "request_hash": "LOWERCASE_SHA256",
    "response_hash": "LOWERCASE_SHA256"
  }),
});
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-runtime/executions/EXECUTIONID/llm-traces.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 => '{
  "provider": "openai",
  "model": "gpt-example",
  "model_version": "2026-09",
  "model_type": "generation",
  "status": "completed",
  "latency_ms": 842,
  "usage": {
    "input_tokens": 320,
    "output_tokens": 84
  },
  "request_hash": "LOWERCASE_SHA256",
  "response_hash": "LOWERCASE_SHA256"
}',
]);
$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.

Raw prompt and response values, when supplied, are used only to calculate hashes and are not persisted. Prefer calculating hashes in your own trust boundary.

Record a tool trace

POST
/2026-02-01/ai-runtime/executions/{executionId}/tool-traces.json
Required permissionai_runtime_executions:write

Path, query, and header parameters

executionIdstring· pathRequired
Running execution that attempted the tool action.

Request Body

JSON
{
  "phase": "completed",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "decision_id": "DECISION_ID"
}
phaseenum
requested, completed, or failed. Default: completed.
actionstringRequired
Namespaced governed action, for example payment.execute.
toolstring
Stable tool identifier.
mcp_serverstring
Stable MCP server identifier.
resourcestring
Non-secret resource reference, up to 500 characters.
decision_idstringRequired
Required for completed actions; must match this execution, action, and tool.
error_codestring
Sanitized failure code when phase is failed.
metadataobject
Sanitized result metadata; do not send arbitrary tool payloads.

Responses

Tool outcome recorded.

{
  "id": "TOOL_TRACE_ID",
  "kind": "tool",
  "phase": "completed",
  "status": "completed",
  "audit_sequence": 6
}

Decision mismatch, denied action, pending approval, or closed execution.

{
  "error": "Tool action is still awaiting approval."
}

A completed action omitted its authorization decision.

{
  "error": "Completed tool traces require an authorization decision."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/tool-traces.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "phase": "completed",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "decision_id": "DECISION_ID"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/tool-traces.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "phase": "completed",
    "action": "payment.execute",
    "tool": "payments-mcp.execute",
    "mcp_server": "payments-mcp",
    "resource": "payment:proposal-123",
    "decision_id": "DECISION_ID"
  }),
});
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-runtime/executions/EXECUTIONID/tool-traces.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 => '{
  "phase": "completed",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "decision_id": "DECISION_ID"
}',
]);
$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.

Read the audit trace

GET
/2026-02-01/ai-runtime/executions/{executionId}/trace.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· pathRequired
Execution whose ordered audit events should be returned.

Responses

Up to 500 tamper-evident audit events ordered by sequence.

{
  "elements": [
    {
      "sequence": 1,
      "event_type": "ai.execution.started",
      "previous_hash": null,
      "current_hash": "SHA256"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/trace.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/trace.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-runtime/executions/EXECUTIONID/trace.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.

Model inventory

GET
/2026-02-01/ai-runtime/models.json
Required permissionai_runtime:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Models observed in company LLM traces.

{
  "elements": [
    {
      "id": "MODEL_ID",
      "provider": "openai",
      "name": "gpt-example",
      "type": "generation"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/models.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/models.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-runtime/models.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
/2026-02-01/ai-runtime/models/{modelId}/usage.json
Required permissionai_runtime:read

Path, query, and header parameters

modelIdstring· pathRequired
Observed company-scoped model ID.

Responses

Model, distinct execution IDs, and associated traces.

{
  "model": {
    "id": "MODEL_ID",
    "provider": "openai",
    "name": "gpt-example"
  },
  "executions": [
    "EXECUTION_ID"
  ],
  "traces": []
}

Model not found in this company.

{
  "error": "AI Model not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/models/MODELID/usage.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/models/MODELID/usage.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-runtime/models/MODELID/usage.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.

Policies and authorization

Policies are evaluated by descending priority with deterministic tie-breaking. Selectors accept exact values, *, and trailing-prefix wildcards. Conditions accept one of eq, gt, gte, lt, lte, or in per field.

List policies

GET
/2026-02-01/ai-runtime/policies.json
Required permissionai_runtime:read

Path, query, and header parameters

No path, query, or additional header parameters.

Responses

Policy versions ordered by name and descending version.

{
  "elements": [
    {
      "id": "POLICY_ID",
      "name": "High-value payment review",
      "version": 1,
      "status": "active",
      "effect": "REQUIRE_APPROVAL"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies.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-runtime/policies.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 policy

POST
/2026-02-01/ai-runtime/policies.json
Required permissionai_runtime:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "name": "High-value payment review",
  "version": 1,
  "status": "active",
  "priority": 100,
  "systems": [
    "SYSTEM_ID"
  ],
  "agents": [
    "AGENT_ID"
  ],
  "actions": [
    "payment.execute"
  ],
  "tools": [
    "payments-mcp.*"
  ],
  "conditions": {
    "amount": {
      "gt": 500
    }
  },
  "effect": "REQUIRE_APPROVAL",
  "rule": "high_value_payment",
  "reason": "Payments above 500 require review."
}
namestringRequired
Policy family name, up to 120 characters.
versioninteger
Positive version. Default: 1; name/version must be unique.
statusenum
draft or active. Default: draft; activating supersedes the previous active version with the same name.
priorityinteger
Evaluation priority from -1000 to 1000.
systems / agentsstring[]
Company-owned IDs, exact values, or wildcard selectors.
actionsstring[]Required
At least one namespaced action selector.
toolsstring[]
Tool selectors; empty matches any tool.
conditionsobject
Context-field conditions with exactly one supported operator each.
effectenumRequired
ALLOW, DENY, or REQUIRE_APPROVAL.
rule / reasonstring
Stable rule label and human-readable decision explanation.

Responses

Policy version created, and activated when requested.

{
  "id": "POLICY_ID",
  "name": "High-value payment review",
  "version": 1,
  "status": "active",
  "effect": "REQUIRE_APPROVAL"
}

This policy name/version already exists.

{
  "error": "This policy version already exists."
}

Selector, condition, or effect is invalid.

{
  "error": "Policy effect must be ALLOW, DENY, or REQUIRE_APPROVAL."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "High-value payment review",
  "version": 1,
  "status": "active",
  "priority": 100,
  "systems": [
    "SYSTEM_ID"
  ],
  "agents": [
    "AGENT_ID"
  ],
  "actions": [
    "payment.execute"
  ],
  "tools": [
    "payments-mcp.*"
  ],
  "conditions": {
    "amount": {
      "gt": 500
    }
  },
  "effect": "REQUIRE_APPROVAL",
  "rule": "high_value_payment",
  "reason": "Payments above 500 require review."
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "High-value payment review",
    "version": 1,
    "status": "active",
    "priority": 100,
    "systems": [
      "SYSTEM_ID"
    ],
    "agents": [
      "AGENT_ID"
    ],
    "actions": [
      "payment.execute"
    ],
    "tools": [
      "payments-mcp.*"
    ],
    "conditions": {
      "amount": {
        "gt": 500
      }
    },
    "effect": "REQUIRE_APPROVAL",
    "rule": "high_value_payment",
    "reason": "Payments above 500 require review."
  }),
});
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-runtime/policies.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": "High-value payment review",
  "version": 1,
  "status": "active",
  "priority": 100,
  "systems": [
    "SYSTEM_ID"
  ],
  "agents": [
    "AGENT_ID"
  ],
  "actions": [
    "payment.execute"
  ],
  "tools": [
    "payments-mcp.*"
  ],
  "conditions": {
    "amount": {
      "gt": 500
    }
  },
  "effect": "REQUIRE_APPROVAL",
  "rule": "high_value_payment",
  "reason": "Payments above 500 require review."
}',
]);
$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.

Retrieve a policy

GET
/2026-02-01/ai-runtime/policies/{policyId}.json
Required permissionai_runtime:read

Path, query, and header parameters

policyIdstring· pathRequired
Company-scoped policy version ID.

Responses

Policy version.

{
  "id": "POLICY_ID",
  "name": "High-value payment review",
  "version": 1,
  "status": "active"
}

Policy not found in this company.

{
  "error": "AI Policy not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies/POLICYID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/policies/POLICYID.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-runtime/policies/POLICYID.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.

Authorize a tool action

POST
/2026-02-01/ai-runtime/authorize.json
Required permissionai_runtime_authorization:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "system_id": "SYSTEM_ID",
  "execution_id": "EXECUTION_ID",
  "agent_id": "AGENT_ID",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "context": {
    "amount": 750,
    "currency": "EUR"
  }
}
system_idstringRequired
System linked to the running execution.
execution_idstringRequired
Running execution linked to this system and agent.
agent_idstringRequired
Active identity linked to the execution.
actionstringRequired
Namespaced action tested against active policies.
toolstring
Stable tool identifier used by policy selectors.
mcp_serverstring
Stable MCP server identifier.
resourcestring
Non-secret resource reference.
contextobject
Sanitized scalar facts evaluated by policy conditions.

Responses

Fail-closed policy decision. REQUIRE_APPROVAL also contains a pending approval.

{
  "decision": "REQUIRE_APPROVAL",
  "decision_id": "DECISION_ID",
  "policy_id": "POLICY_ID",
  "policy_version": 1,
  "rule": "high_value_payment",
  "reason": "Payments above 500 require review.",
  "approval_id": "APPROVAL_ID",
  "approval": {
    "status": "pending"
  }
}

Resource relationships, execution state, or idempotency conflict.

{
  "error": "Execution does not belong to the requested Agent Identity."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/authorize.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "system_id": "SYSTEM_ID",
  "execution_id": "EXECUTION_ID",
  "agent_id": "AGENT_ID",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "context": {
    "amount": 750,
    "currency": "EUR"
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/authorize.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "system_id": "SYSTEM_ID",
    "execution_id": "EXECUTION_ID",
    "agent_id": "AGENT_ID",
    "action": "payment.execute",
    "tool": "payments-mcp.execute",
    "mcp_server": "payments-mcp",
    "resource": "payment:proposal-123",
    "context": {
      "amount": 750,
      "currency": "EUR"
    }
  }),
});
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-runtime/authorize.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 => '{
  "system_id": "SYSTEM_ID",
  "execution_id": "EXECUTION_ID",
  "agent_id": "AGENT_ID",
  "action": "payment.execute",
  "tool": "payments-mcp.execute",
  "mcp_server": "payments-mcp",
  "resource": "payment:proposal-123",
  "context": {
    "amount": 750,
    "currency": "EUR"
  }
}',
]);
$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.

A decision is not tool execution

Donutwork records and governs intent; it never performs the requested tool action. Your application must enforce the decision and record the eventual outcome.

Human approvals

List approvals

GET
/2026-02-01/ai-runtime/approvals.json
Required permissionai_runtime_approvals:read

Path, query, and header parameters

statusenum· query
pending, approved, or rejected. Omit to return all states.

Responses

Approvals ordered newest first.

{
  "elements": [
    {
      "id": "APPROVAL_ID",
      "execution_id": "EXECUTION_ID",
      "action": "payment.execute",
      "status": "pending"
    }
  ]
}

Unsupported status filter.

{
  "error": "Approval status filter is invalid."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/approvals.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/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-runtime/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.

Retrieve an approval

GET
/2026-02-01/ai-runtime/approvals/{approvalId}.json
Required permissionai_runtime_approvals:read

Path, query, and header parameters

approvalIdstring· pathRequired
Company-scoped approval ID.

Responses

Approval and decision linkage.

{
  "id": "APPROVAL_ID",
  "decision_id": "DECISION_ID",
  "status": "pending",
  "requested_at": "2026-09-18T10:00:04+00:00"
}

Approval not found in this company.

{
  "error": "AI Approval not found."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/approvals/APPROVALID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/approvals/APPROVALID.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-runtime/approvals/APPROVALID.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

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

Path, query, and header parameters

approvalIdstring· pathRequired
Pending company-scoped approval ID.

Request Body

JSON
{
  "decision": "approved",
  "reason": "Reviewed against purchase order PO-8472"
}
decisionenumRequired
approved or rejected.
reasonstring
Human-readable rationale, up to 1,000 characters.

Responses

One-shot decision recorded; decided_by is derived from the authenticated API key.

{
  "id": "APPROVAL_ID",
  "status": "approved",
  "decided_by": "API_KEY_ID",
  "reason": "Reviewed against purchase order PO-8472"
}

Approval already resolved or execution closed before approval.

{
  "error": "Approval is already resolved as approved."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/approvals/APPROVALID/decision.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "decision": "approved",
  "reason": "Reviewed against purchase order PO-8472"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/approvals/APPROVALID/decision.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "decision": "approved",
    "reason": "Reviewed against purchase order PO-8472"
  }),
});
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-runtime/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",
  "reason": "Reviewed against purchase order PO-8472"
}',
]);
$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.

Evaluations

GET
/2026-02-01/ai-runtime/evaluations.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· query
Restrict results to one company-owned execution.

Responses

Evaluations ordered newest first.

{
  "elements": [
    {
      "id": "EVALUATION_ID",
      "name": "policy-conformance",
      "score": 0.98,
      "threshold": 0.95,
      "passed": true
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/evaluations.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/evaluations.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-runtime/evaluations.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.

POST
/2026-02-01/ai-runtime/evaluations.json
Required permissionai_runtime_executions:write

Path, query, and header parameters

No path, query, or additional header parameters.

Request Body

JSON
{
  "execution_id": "EXECUTION_ID",
  "trace_id": "TRACE_ID",
  "name": "policy-conformance",
  "type": "threshold",
  "score": 0.98,
  "threshold": 0.95,
  "severity": "high",
  "target_type": "llm_trace"
}
execution_idstringRequired
Company-owned execution being evaluated.
trace_idstring
Trace belonging to the same execution.
model_idstring
Observed company-owned model.
namestringRequired
Stable evaluation name, up to 160 characters.
typestring
Only threshold is currently supported.
score / thresholdnumberRequired
Numbers from 0 to 1. passed is computed as score >= threshold.
severitystring
Failure severity; ignored for passing results.
target_typeenum
execution, llm_trace, tool_action, output, or model.
metadataobject
Sanitized evaluator metadata.

Responses

Deterministic result recorded.

{
  "id": "EVALUATION_ID",
  "evaluator": "deterministic.threshold",
  "evaluator_version": "1",
  "score": 0.98,
  "threshold": 0.95,
  "passed": true,
  "severity": "none"
}

Unsupported evaluator or out-of-range values.

{
  "error": "Evaluation score and threshold must be between 0 and 1."
}

Request examples

curl --request POST 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/evaluations.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "execution_id": "EXECUTION_ID",
  "trace_id": "TRACE_ID",
  "name": "policy-conformance",
  "type": "threshold",
  "score": 0.98,
  "threshold": 0.95,
  "severity": "high",
  "target_type": "llm_trace"
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/evaluations.json', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "execution_id": "EXECUTION_ID",
    "trace_id": "TRACE_ID",
    "name": "policy-conformance",
    "type": "threshold",
    "score": 0.98,
    "threshold": 0.95,
    "severity": "high",
    "target_type": "llm_trace"
  }),
});
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-runtime/evaluations.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 => '{
  "execution_id": "EXECUTION_ID",
  "trace_id": "TRACE_ID",
  "name": "policy-conformance",
  "type": "threshold",
  "score": 0.98,
  "threshold": 0.95,
  "severity": "high",
  "target_type": "llm_trace"
}',
]);
$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.

Controls, compliance mappings, and evidence

These endpoints report technical evidence. They are not legal advice, do not decide whether a regulation applies, and do not certify compliance.

GET
/2026-02-01/ai-runtime/executions/{executionId}/controls.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· pathRequired
Execution assessed against reusable technical controls.

Responses

Evidence availability for logging, identity, models, policy, oversight, evaluation, audit, and tools.

{
  "elements": [
    {
      "id": "audit_integrity",
      "status": "evidence_available"
    },
    {
      "id": "human_oversight",
      "status": "evidence_missing"
    }
  ]
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/controls.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/controls.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-runtime/executions/EXECUTIONID/controls.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
/2026-02-01/ai-runtime/executions/{executionId}/compliance/{packSlug}.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· pathRequired
Execution assessed against a technical mapping pack.
packSlugstring· pathRequired
Configured pack slug for EU AI Act, ISO/IEC 42001, or NIST AI RMF mapping.

Responses

Requirements mapped to available and missing evidence.

{
  "slug": "eu-ai-act",
  "execution_id": "EXECUTION_ID",
  "requirements": [
    {
      "technical_status": "partial",
      "evidence_available": [
        "audit_integrity"
      ],
      "evidence_missing": [
        "human_oversight"
      ]
    }
  ],
  "disclaimer": "Technical mappings only; not legal advice and not a certification of compliance."
}

Unknown compliance pack.

{
  "error": "Unsupported compliance pack."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/compliance/PACKSLUG.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/executions/EXECUTIONID/compliance/PACKSLUG.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-runtime/executions/EXECUTIONID/compliance/PACKSLUG.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.

Export execution evidence

GET
/2026-02-01/ai-runtime/evidence/executions/{executionId}.json
Required permissionai_runtime:read

Path, query, and header parameters

executionIdstring· pathRequired
Execution exported as a portable evidence document.

Responses

Evidence schema v1 with runtime facts, hashes, verification, controls, and mappings.

{
  "schema": "donutwork.ai-runtime.evidence",
  "schema_version": "1.0",
  "tenant": {
    "company_id": "COMPANY_ID"
  },
  "execution": {
    "id": "EXECUTION_ID"
  },
  "audit_verification": {
    "valid": true
  },
  "consistency": {
    "mode": "audit_head_snapshot",
    "status": "verified"
  }
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/evidence/executions/EXECUTIONID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/evidence/executions/EXECUTIONID.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-runtime/evidence/executions/EXECUTIONID.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.

Export system evidence

GET
/2026-02-01/ai-runtime/evidence/systems/{systemId}.json
Required permissionai_runtime:read

Path, query, and header parameters

systemIdstring· pathRequired
Company-scoped system whose recent executions are exported.
fromISO 8601· query
Include executions started at or after this timestamp.
toISO 8601· query
Include executions started at or before this timestamp.

Responses

Up to 100 matching execution evidence documents.

{
  "schema": "donutwork.ai-runtime.system-evidence",
  "schema_version": "1.0",
  "filter": {
    "from": null,
    "to": null
  },
  "system": {
    "id": "SYSTEM_ID"
  },
  "executions": []
}

from or to is not a valid timestamp.

{
  "error": "Evidence time filters must be valid timestamps."
}

Request examples

curl --request GET 'https://api.hub.donutwork.com/2026-02-01/ai-runtime/evidence/systems/SYSTEMID.json' \
  --header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/ai-runtime/evidence/systems/SYSTEMID.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-runtime/evidence/systems/SYSTEMID.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.

Evidence excludes raw prompts, responses, documents, tool payloads, headers, credentials, and idempotency keys. consistency.status=verified means the exported audit events matched the chain head read for that export; retry an incomplete_or_concurrent_change export before archiving it.

On this page