DEMO / RECORDED 2026-09-21
One supervised write,
start to receipt.
An MCP client (the agent) releases a supplier hold in a records system: it reads the record, proposes one PATCH bound to the version it read, waits while the workspace owner approves the exact request, and receives the provider's response. Recorded against the free sandbox on 2026-09-21 between 03:32:57 and 03:35:12 UTC. Every block below is what the API returned; only the API key and the owner's email are redacted.
The same loop applies to any owner-approved connection, including Slack team notifications and Stripe test-mode billing records; see the connection rules. Nothing here left ActionDock: the sandbox connection points at a built-in records API.
Connect over MCP
The client connects to https://actiondock.app/mcp with a workspace API key and lists the tools and the owner-approved connections. The connection carries no secret: hasCredential is the most the agent ever sees.
{
"tools": [
"list_actions",
"run_action",
"get_job",
"list_job_reconciliations",
"wait_for_job",
"list_events",
"list_connections",
"list_usage"
]
}{
"id": "7b9288c7-f162-4622-a8be-fd9c9ac70089",
"name": "Sandbox records API",
"description": "Built-in test API with supplier records. Reads and writes stay inside ActionDock; nothing reaches an external system.",
"baseUrl": "https://actiondock.app/sandbox/7dc23349-161d-4e09-bd4d-0abd2d76414e/v1/",
"authType": "api_key",
"authHeaderName": "X-Sandbox-Key",
"hasCredential": true,
"allowedMethods": [
"GET",
"POST",
"PATCH"
],
"allowedPathPrefixes": [
"/records"
],
"writePolicyMode": "manual_only"
}Read before you write
Reads run without approval. The response carries the provider's ETag, so the write can be bound to exactly this version.
{
"action": "integration.fetch",
"input": {
"connectionId": "7b9288c7-f162-4622-a8be-fd9c9ac70089",
"path": "/records/sup_1002"
},
"workflow_name": "Supplier hold review"
}{
"id": "16461972-67a8-432a-a694-444bfb912a28",
"status": "queued",
"sideEffect": false
}{
"id": "16461972-67a8-432a-a694-444bfb912a28",
"action": "integration.fetch",
"status": "succeeded",
"sideEffect": false,
"output": {
"ok": true,
"statusCode": 200,
"headers": {
"etag": "\"v1\"",
"contentType": "application/json; charset=utf-8",
"lastModified": null
},
"body": {
"id": "sup_1002",
"kind": "supplier",
"name": "Merlion Packaging",
"notes": "On hold pending updated insurance certificate.",
"status": "on_hold",
"version": 1,
"createdAt": "2026-09-01T09:00:00.000Z",
"updatedAt": "2026-09-01T09:00:00.000Z",
"contactEmail": "accounts@merlion.example",
"paymentTermsDays": 45
}
}
}Propose the write
The agent submits the PATCH with ifMatch from the read and an idempotency key. ActionDock resolves the exact target URL, binds it to the connection version, and parks the job as awaiting_approval. The preview it returns is the same object the owner is asked to approve.
{
"action": "integration.execute",
"input": {
"body": {
"notes": "Insurance certificate received 2026-09-21; hold released.",
"status": "active"
},
"path": "/records/sup_1002",
"method": "PATCH",
"ifMatch": "\"v1\"",
"connectionId": "7b9288c7-f162-4622-a8be-fd9c9ac70089"
},
"workflow_name": "Supplier hold review",
"idempotency_key": "supplier-hold-release-sup_1002-2026-09-21"
}{
"id": "9f4499b0-7362-4277-8b3e-5ccee03d5e68",
"action": "integration.execute",
"status": "awaiting_approval",
"sideEffect": true,
"output": {
"body": {
"notes": "Insurance certificate received 2026-09-21; hold released.",
"status": "active"
},
"kind": "approved_api_write",
"path": "/records/sup_1002",
"query": {},
"method": "PATCH",
"baseUrl": "https://actiondock.app/sandbox/7dc23349-161d-4e09-bd4d-0abd2d76414e/v1/",
"ifMatch": "\"v1\"",
"targetUrl": "https://actiondock.app/sandbox/7dc23349-161d-4e09-bd4d-0abd2d76414e/v1/records/sup_1002",
"connectionId": "7b9288c7-f162-4622-a8be-fd9c9ac70089",
"connectionName": "Sandbox records API",
"writePolicyMode": "manual_only",
"connectionVersion": 1,
"reasonablenessPolicyConfigured": false
}
}While the job waits, wait_for_job blocks for up to the timeout you pass (keep it under your client's tool timeout) and returns awaiting_approval if nothing has changed; call it again. The owner also receives an email that a write awaits review.
The owner approves the exact request
In the dashboard the owner sees the agent's input and the bound preview side by side, with two buttons. The agent's key cannot approve; only a signed-in owner can. Approval was recorded at 03:35:12 UTC, 2 minutes and 14 seconds after the proposal.
INPUT
{
"body": {
"notes": "Insurance certificate received 2026-09-21; hold released.",
"status": "active"
},
"path": "/records/sup_1002",
"method": "PATCH",
"ifMatch": "\"v1\"",
"connectionId": "7b9288c7-f162-4622-a8be-fd9c9ac70089"
}
RESULT
{
"kind": "approved_api_write",
"connectionName": "Sandbox records API",
"connectionVersion": 1,
"method": "PATCH",
"targetUrl": "https://actiondock.app/sandbox/7dc23349-161d-4e09-bd4d-0abd2d76414e/v1/records/sup_1002",
"ifMatch": "\"v1\"",
"body": { "status": "active", "notes": "Insurance certificate received 2026-09-21; hold released." },
"writePolicyMode": "manual_only"
}
[ APPROVE WRITE ] [ REJECT WRITE ]Execution receipt
After approval ActionDock executes the request with the server-held credential and returns the provider's response: status 200, ETag moved from "v1" to "v2", record now active. Had the provider accepted the write but the response been lost, the job would end as execution_unknown with retries suppressed, not as a blind retry.
{
"id": "9f4499b0-7362-4277-8b3e-5ccee03d5e68",
"status": "succeeded",
"approvedAt": "2026-09-21T03:35:12.145Z",
"approvedBy": "<owner email>",
"output": {
"ok": true,
"statusCode": 200,
"headers": {
"etag": "\"v2\"",
"contentType": "application/json; charset=utf-8",
"lastModified": null
},
"body": {
"id": "sup_1002",
"kind": "supplier",
"name": "Merlion Packaging",
"notes": "Insurance certificate received 2026-09-21; hold released.",
"status": "active",
"version": 2,
"createdAt": "2026-09-01T09:00:00.000Z",
"updatedAt": "2026-09-21T03:35:12.216Z",
"contactEmail": "accounts@merlion.example",
"paymentTermsDays": 45
},
"billing": {
"status": "metered",
"amountUsd": 0.01,
"lines": [
{
"unit": "completed action",
"quantity": 1,
"amountUsd": 0.01,
"unitPriceUsd": 0.01
}
]
}
}
}Event log
Every state change is an event the agent can list or receive through a signed callback.
[
{
"type": "job.approval_requested",
"createdAt": "2026-09-21T03:32:58.304Z",
"to": "<owner email>"
},
{
"type": "job.approved",
"createdAt": "2026-09-21T03:35:12.157Z",
"approvedBy": "<owner email>"
},
{
"type": "job.succeeded",
"createdAt": "2026-09-21T03:35:12.246Z"
}
]What the run shows
- CredentialThe agent authenticated to ActionDock only; the records API key stayed encrypted on the server and never appeared in any payload.
- Exact approvalThe owner approved a resolved target URL, method, body, and
If-Matchbound to connection version 1, not a description of an intent. - ConcurrencyThe write carried the ETag from the read; a change in between would have returned 412 instead of overwriting.
- IdempotencyThe submission key deduplicates the ActionDock job; the provider's own idempotency is separate.
- Uncertain outcomesA lost provider response ends as
execution_unknownwith reconciliation evidence, so nothing is retried blindly.
Run it yourself
Create a free sandbox (email only, no card), create an API key in the dashboard, and point any MCP client at https://actiondock.app/mcp. The agent guide has the one-line Claude Code install and the HTTP equivalents of every call above.