Integrations & Capabilities
Orchestrate third-party application lifecycle, declare functional capabilities, and execute delegated actions.
Integrations & Capabilities API
The Integrations API lets a third-party application declare callable capabilities and expose them as managed actions inside Donutwork. Donutwork validates declared inputs, applies the configured application authentication, relays the request, and records operational metadata.
Lifecycle Management
Audit Active Integrations
Retrieve a paginated directory of all integrations currently authorized for your organization. This includes high-level connectivity status and identification handlers for each service.
integrations:readPath, query, and header parameters
sizepageResponses
Integration directory successfully retrieved.
{
"entities": "Integration",
"count": 1,
"per_page": 100,
"pages": {
"current": 1,
"max": 1
},
"elements": [
{
"id": "6908bffd...",
"handler": "STRIPE_CONNECT",
"name": "Stripe Global Payments",
"status": "connected"
}
]
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/integrations.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations.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/integrations.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.
Declare Functional Capabilities
Register or update the actions (capabilities) exposed by your application. The declaration defines the input contract Donutwork validates before proxying a request to your service. You can also declare actions that appear in the backoffice and open your embedded app.
integrations:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"app_token": "ext_auth_token_9921",
"capabilities": [
{
"name": "SYNC_INVENTORY",
"method": "POST",
"endpoint": "https://api.myapp.com/v1/sync",
"scopes": [
"workflow",
"api"
],
"inputs": [
{
"name": "sku",
"type": "string",
"required": true
},
{
"name": "quantity",
"type": "number",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}capabilitiesui_actionseventsapp_tokenResponses
Capabilities successfully registered.
{
"success": true,
"message": "Capabilities declared successfully for MY_CUSTOM_APP",
"app_handler": "MY_CUSTOM_APP",
"actions_count": 1,
"ui_actions_count": 1,
"events_count": 0
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/integrations.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"app_token": "ext_auth_token_9921",
"capabilities": [
{
"name": "SYNC_INVENTORY",
"method": "POST",
"endpoint": "https://api.myapp.com/v1/sync",
"scopes": [
"workflow",
"api"
],
"inputs": [
{
"name": "sku",
"type": "string",
"required": true
},
{
"name": "quantity",
"type": "number",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations.json', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"app_token": "ext_auth_token_9921",
"capabilities": [
{
"name": "SYNC_INVENTORY",
"method": "POST",
"endpoint": "https://api.myapp.com/v1/sync",
"scopes": [
"workflow",
"api"
],
"inputs": [
{
"name": "sku",
"type": "string",
"required": true
},
{
"name": "quantity",
"type": "number",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}),
});
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/integrations.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 => '{
"app_token": "ext_auth_token_9921",
"capabilities": [
{
"name": "SYNC_INVENTORY",
"method": "POST",
"endpoint": "https://api.myapp.com/v1/sync",
"scopes": [
"workflow",
"api"
],
"inputs": [
{
"name": "sku",
"type": "string",
"required": true
},
{
"name": "quantity",
"type": "number",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}',
]);
$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.
capabilities[].scopes accepts "workflow", "api", or a non-empty array containing either or both. workflow makes the capability available to workflows; api allows calls through the integration proxy endpoints below. Omit scopes to preserve the existing unrestricted behavior, including Copilot access. An explicit scope list excludes Copilot.
ui_actions is separate from proxied capabilities. Each entry requires a unique key (starts with a letter; then letters, digits, _, . or -; at most 100 characters), a non-empty label (at most 80 characters), and a non-empty placements array. Up to 20 UI actions can be declared. Currently customer:details is the only supported placement. Omitting ui_actions keeps the existing declaration; [] removes all UI actions. Every PUT still replaces capabilities, events, and app_token, so include their current values if they must remain active. See Iframe Context & Security for launch parameters and verification.
Resource Discovery
Retrieve Specific Integration
Fetch an integration's API-visible proxied actions, declared UI actions, and events. The app token is not included.
integrations:readPath, query, and header parameters
appHandlerRequiredResponses
Detailed integration profile retrieved.
{
"id": "6908...",
"app_handler": "MY_CUSTOM_APP",
"actions": [
{
"name": "SYNC_INVENTORY",
"method": "POST",
"scopes": [
"workflow",
"api"
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
],
"events": []
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER.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/integrations/APPHANDLER.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.
Execution (Capability Proxy)
Dispatch Delegated Action
Execute a capability on a remote integration. Donutwork applies the configured authorization and validates declared inputs before relaying the request to the target service.
integrations:writePath, query, and header parameters
appHandlerRequiredappActionRequiredResponses
Action executed and external response relayed.
{
"success": true,
"integration": "SLACK_ENT",
"action": "SEND_MESSAGE",
"external_status": 200,
"response": {
"ok": true,
"ts": "123456789.0001"
}
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.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/integrations/APPHANDLER/APPACTION.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.
The request payload for action dispatch is dynamic. Required body fields are validated at runtime using the integration capability declaration (capabilities[].inputs[] with required: true).
Read Delegated Action
integrations:readPath, query, and header parameters
appHandlerRequiredappActionRequiredResponses
Action executed using GET forwarding.
{
"success": true,
"integration": "SLACK_ENT",
"action": "GET_CHANNEL_INFO",
"external_status": 200,
"response": {}
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.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/integrations/APPHANDLER/APPACTION.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 Delegated Action
integrations:writePath, query, and header parameters
appHandlerRequiredappActionRequiredResponses
Action executed using PUT forwarding.
{
"success": true,
"integration": "SLACK_ENT",
"action": "UPSERT_TEMPLATE",
"external_status": 200,
"response": {}
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.json', {
method: 'PUT',
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/integrations/APPHANDLER/APPACTION.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'PUT',
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 Delegated Action
integrations:writePath, query, and header parameters
appHandlerRequiredappActionRequiredResponses
Action executed using DELETE forwarding.
{
"success": true,
"integration": "SLACK_ENT",
"action": "DELETE_MESSAGE",
"external_status": 200,
"response": {}
}Request examples
curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/APPACTION.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/integrations/APPHANDLER/APPACTION.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.
Emit Integration Event
Emit an application-scoped event so workflows and event listeners can react to integration activity.
integration_events:writePath, query, and header parameters
appHandlerRequiredeventNameRequiredRequest Body
{
"metadata": {
"message_id": "msg_001",
"status": "sent"
},
"entity_id": "SLACK_ENT"
}metadataentity_idResponses
Event accepted and dispatched.
{
"success": true,
"app_handler": "SLACK",
"event": "Slack.MESSAGE_SENT",
"queued_workflows": 1
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/events/EVENTNAME/emit.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"metadata": {
"message_id": "msg_001",
"status": "sent"
},
"entity_id": "SLACK_ENT"
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations/APPHANDLER/events/EVENTNAME/emit.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"metadata": {
"message_id": "msg_001",
"status": "sent"
},
"entity_id": "SLACK_ENT"
}),
});
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/integrations/APPHANDLER/events/EVENTNAME/emit.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 => '{
"metadata": {
"message_id": "msg_001",
"status": "sent"
},
"entity_id": "SLACK_ENT"
}',
]);
$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.