workflows-api¶
Status: Current | Last reviewed: 2026-10-13
workflows-api is the public API for workflow event ingest, direct workflow execution, execution inspection, human-in-the-loop actions, and execution event streaming. The service implementation is named async-agent in repo and deployment internals, but integration teams should refer to the external API as workflows-api.
Quick reference¶
| Detail | Value |
|---|---|
| Base URL | https://events.vanguard.dev.trueserve.deloitte.com |
| Authentication | Bearer tsf_* API token (tenant-scoped) |
| Required scopes | Event ingestion, saved workflow execution and execution inspection/control require agents:run; agents:manage includes it |
Per-workflow OpenAPI specs served by ai-manager at /api/workflows/<workflow_id>/openapi.json are also tenant-scoped. Fetch them with an ai-manager session or a tenant Bearer tsf_* token carrying agents:run; a token for one tenant cannot read another tenant's workflow contract.
What it covers¶
| Capability | Endpoint |
|---|---|
| Generic event ingest | POST /api/v1/events |
| Direct workflow execution | POST /api/v1/workflows/{workflow_config_id}/execute |
| Poll a workflow receipt or run | GET /api/v1/workflow-runs/{run_id} |
| List and inspect executions | GET /api/v1/executions, GET /api/v1/executions/{execution_id} |
| Read the execution event log | GET /api/v1/executions/{execution_id}/events |
| Stream live execution updates | GET /sse/executions/{execution_id} |
| Resume human/tool input gates | POST /api/v1/executions/{execution_id}/approve, /override, /tool-input |
Execution list/detail/event/SSE and human-in-the-loop routes are tenant-scoped.
A token for one tenant receives 404 for another tenant's execution id rather
than cross-tenant execution data.
The two ingest patterns¶
Direct execution is also available when the workflow is already selected.
POST {"execution_mode":"async","inputs":{...}} to
/api/v1/workflows/{numeric_id}/execute. A 200 response with
status: "submitted" and execution_id means the execution and dispatch
intent are durable. Publication may still be pending during a broker outage.
The admitted root configuration/version is retained for execution and recovery.
Send an optional Idempotency-Key header (1–128 characters). Reusing it in
the same tenant/workflow with identical inputs, caller chain and execution
overrides returns the same execution. Changed arguments return 409.
Use a new key for new work. This contract applies to asynchronous direct
execution; it does not add idempotency to synchronous calls or generic events.
Poll /api/v1/workflow-runs/{execution_id} with the same tenant credential.
The result contains execution_id, workflow_config_id, nullable run_id,
status, nullable timestamps, run metadata and an execution SSE
progress_stream path. The route also accepts a specific run UUID. A queued
receipt has no run yet; a newly allocated run remains pending with no start
time until its first useful node begins. metadata.startup contains trusted
startup timestamps once recorded. Another tenant's receipt returns 404.
workflows-api takes in events through two distinct paths:
Generic events: POST /api/v1/events¶
For partner systems, custom scripts, and backend services. Authenticate with a tenant-scoped Bearer tsf_* token carrying the agents:run scope. The body is lean: supply event_type, source, and a payload. Routing to a workflow happens server-side by matching event_type against the tenant's configured trigger rules.
curl -X POST https://events.vanguard.dev.trueserve.deloitte.com/api/v1/events \
-H "Authorization: Bearer tsf_..." \
-H "Content-Type: application/json" \
-d '{
"event_type": "ticket.escalated",
"source": "partner-helpdesk",
"payload": {
"ticket_id": "5003X00001abcd",
"priority": "high"
}
}'
The full request schema (CreateEventRequest):
| Field | Required | Notes |
|---|---|---|
event_type |
yes | Matched against trigger rules, e.g. ticket.escalated. |
source |
yes | Origin label, e.g. partner-helpdesk. |
payload |
no | Event body; addressed by workflows as {{trigger.<field>}}. Defaults to {}. |
metadata |
no | Optional extras. tenant_id is injected server-side from the token. |
event_id and timestamp are generated server-side — you don't send them. The response is 202 Accepted with {"status": "accepted", "event_id": "..."} returning the assigned id (or "duplicate" if that id was seen in the last 24h — relevant for adapter-sourced events that carry a stable id). The endpoint does not return an execution_id — accepting an event is decoupled from whether (and how many) workflows it triggers. To observe the resulting run, list or stream executions: GET /api/v1/executions?status=... then GET /sse/executions/{execution_id}.
The token's tenant is authoritative. If you also set metadata.tenant_id it must match the token, or the request is rejected with 403.
Resuming a Wait node with an event¶
A workflow Wait node can pause a running execution until a matching event arrives. The event is sent to the same generic endpoint, but its payload must identify the running workflow execution to resume. Put the target execution id in payload.workflow_execution_id or payload.execution_id.
curl -X POST https://events.vanguard.dev.trueserve.deloitte.com/api/v1/events \
-H "Authorization: Bearer tsf_..." \
-H "Content-Type: application/json" \
-d '{
"event_type": "ticket.updated",
"source": "partner-helpdesk",
"payload": {
"workflow_execution_id": "8f3f06a2-6fd7-44b8-8ac6-1f421b9fca8d",
"ticket_id": "5003X00001abcd",
"status": "resolved",
"notes": "Customer replied"
}
}'
event_type must match the Wait node's Event type. source remains an origin label for observability; Wait-node matching does not require authors to choose a source in the editor. The optional Event filter matches dot paths inside payload, so a filter key of status with value resolved only resumes the wait for payloads with "status": "resolved".
When the workflow resumes, the Wait node output includes the payload both flattened and under payload. If the Wait node id is wait_for_ticket, downstream blocks can reference {{wait_for_ticket.ticket_id}}, {{wait_for_ticket.status}}, or the full nested form {{wait_for_ticket.payload.ticket_id}}.
Provider events: push adapters (no inbound webhook)¶
Provider integrations — Salesforce Pub/Sub, AWS EventBridge, Google Pub/Sub — do not post to a workflow webhook URL. The platform runs a background subscriber that pulls from the provider's stream, normalizes each provider event into the same InboundEvent envelope, and hands it to the identical trigger-matching path the generic endpoint uses. There is no /api/v1/webhooks/* route — provider auth is the subscription's own credential, configured on the integration, not a bearer token on a request.
Internal normalized envelope¶
Both paths normalize to an internal InboundEvent before trigger evaluation. Callers don't construct this — it's what the generic request and provider adapters both produce:
{
"event_id": "<server-generated-uuid>",
"event_type": "ticket.escalated",
"source": "partner-helpdesk",
"source_integration_id": null,
"timestamp": "2026-05-28T13:18:00Z",
"payload": { "ticket_id": "5003X00001abcd", "priority": "high" },
"metadata": { "tenant_id": "..." },
"raw_event": null
}
source_integration_id and raw_event are populated only by provider adapters (they record which integration emitted the event and the untouched provider payload); the generic HTTP path leaves them null. Workflows address the body as {{trigger.<field>}} in their nodes — e.g. {{trigger.ticket_id}} once trigger field-mappings project payload.ticket_id into the workflow input.
Execution SSE events¶
GET /sse/executions/{execution_id} streams workflow execution progress. Each SSE frame uses the Redis stream id as id:, the event kind as event:, and a JSON object as data:.
Common event: values:
| Event | When it appears | Important data |
|---|---|---|
execution_created |
A workflow run is created | execution_id, workflow metadata |
node_completed |
A node finishes | node id/type, output summary, timing |
workflow_completed |
The run reaches End | final outputs |
workflow_error |
The run fails | error message and context |
waiting |
A Wait node suspends the run | awaited event/duration details |
workflow_waiting_human / human_review_requested |
Human approval is required | review prompt and action options |
workflow_waiting_input / tool_input_requested |
A tool-input gate is waiting | requested fields |
tool_use |
An LLM/tool node invokes a tool | tool name, arguments/result summary |
doom_loop |
Loop protection fires | loop diagnostic details |
human_approved, human_rejected, human_override |
A reviewer resolves a gate | reviewer action payload |
tool_input_provided |
External input resumes a gate | provided fields |
Clients should ignore unknown event types and use Last-Event-ID when reconnecting.
Live OpenAPI¶
The block below renders the running service's /openapi.json, grouped into three tags:
- Events — generic event ingest.
- Workflows — direct workflow execution (sync or async).
- Executions — introspection of an in-flight or completed run: status, event log, communications, human-in-the-loop approve/override, tool-input gates, and the SSE live stream.
If the live service is unavailable while the docs build, the generator keeps the last successful spec when one exists so this reference does not collapse to an empty OpenAPI page.
Idempotency¶
Asynchronous direct execution uses the Idempotency-Key contract above.
Its deduplication lasts as long as the execution record is retained; it has
no fixed 24-hour expiration.
The platform deduplicates by the normalized event's event_id for 24 hours. For the generic HTTP endpoint the server assigns a fresh event_id per request, so each accepted POST is a distinct event — retry only if you got no 202.
Provider adapters set a stable event_id derived from the upstream record (e.g. Salesforce Pub/Sub uses the platform event's replay ID), so adapter-sourced retries naturally dedupe and never double-trigger a workflow.
See also¶
Preview a Decision¶
POST /api/v1/decisions/test accepts {node, sample}. node contains the router id/type/config and optional node-level integration_id. sample accepts input, vars, node_results, trigger, and iterator objects. The response is the selected node result, including route_value and AI answers when applicable. This endpoint requires a tenant-scoped agents:run token; a supplied X-TrueServe-Tenant must match it. Invalid configuration returns 400, invalid request shape returns 422, and tenant mismatch returns 403.
Studio calls the rate-limited POST /api/workflows/decisions/test proxy with workflow-authoring permission. The browser never receives integration credentials. The preview executes no downstream nodes.