Linker Workflow Orchestration API
Gateway prefix: ${API_BASE}/linker/...
Backend service: ai-link-service
Core controllers:
WorkflowController(/api/workflows)TriggerController(/api/triggers)TaskReportController(/api/task)
Positioning and typical scenarios
AI-Link provides a visual, orchestratable workflow engine that links:
- AI capabilities (LLM, function calling)
- Data capabilities (query, write)
- External systems (HTTP APIs, messages, webhooks)
into observable and traceable business processes, for example:
- approval flows (automatic decisions + human approvals)
- CRM lead routing and follow-up reminders
- ETL preprocessing and AI quality checks
Core concepts
- Workflow
- Node graph composed of Start/End/HTTP/Condition/Loop/Code/Approval/LLM nodes, etc.
- Each workflow has a unique
apiKeyused for external execution.
- Trigger
- Decoupled from workflows; multiple triggers can be configured for the same workflow.
- Types:
WEBHOOK,CRON,MQ
- Execution
- Each run produces an
executionIdfor logs and approvals.
- Each run produces an
- HumanTask
- Approval nodes in the workflow produce pending tasks that require human action before continuing.
Workflow management and execution (/api/workflows)
Create workflow
- POST
/linker/workflows - Request body:
Workflowobject
Key fields:
name: workflow namedescription: descriptionnodes: node array (start, end, http, approval, llm, etc.)edges: edge array describing wiring between nodesmetadata: arbitrary JSON metadata
Node data/meta structures vary by node type. It is recommended to use the Linker visual editor to generate/maintain them instead of editing JSON manually.
Example:
curl -X POST "${API_BASE}/linker/workflows" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Approval Workflow",
"apiKey": "order_approval_v1",
"description": "Order approval flow",
"nodes": [],
"edges": []
}'
Common API list
- Create: POST
/linker/workflows - Update: PUT
/linker/workflows/<id> - Get details: GET
/linker/workflows/<id> - Paged list: GET
/linker/workflows?name=&status=&page=&size= - Publish: POST
/linker/workflows/<id>/publish - Activate: POST
/linker/workflows/<id>/activate - Deactivate: POST
/linker/workflows/<id>/deactivate - Copy: POST
/linker/workflows/<id>/copy?newName=<newName> - Validate: POST
/linker/workflows/validate - Execute: POST
/linker/workflows/<apiKey>/execute
Trigger management (/api/workflows/{workflowId}/triggers, /api/triggers)
- List: GET
/linker/workflows/<workflowId>/triggers - Create: POST
/linker/workflows/<workflowId>/triggers - Get: GET
/linker/triggers/<triggerId> - Update: PUT
/linker/triggers/<triggerId> - Delete: DELETE
/linker/triggers/<triggerId> - View event inbox: GET
/linker/triggers/<triggerId>/events?page=&size= - Webhook trigger: POST
/linker/triggers/webhook/<triggerId>
Trigger object structure (TriggerDef)
Common fields (see backend TriggerDefEntity and TriggerController.TriggerDefDetail):
id: trigger IDtype: trigger type enum:WEBHOOK: triggered via HTTP webhookCRON: scheduled Cron-based triggerMQ: message-queue trigger
name: nameenabled: whether enabledworkflowApiKey: bound workflowapiKeyinputTemplate: input template for mapping trigger events to workflow Start inputs
Webhook-specific fields:
webhookSignatureHeader: signature header name (defaultX-Signature)webhookTimestampHeader: timestamp header name (defaultX-Timestamp)webhookMaxSkewSeconds: allowed time skew in seconds (default ~300s)webhookHasSecret: whether a webhook secret is configured (read-only, for UI)
Cron-specific fields:
cronExpression: Cron expression (for example0 */5 * * * ?)cronTimezone: timezone (for exampleAsia/Shanghai)cronNextFireAt: next scheduled fire time (read-only, calculated by scheduler)
MQ-specific fields:
mqDriver: message driver type (for examplekafka,rabbitmq, implementation-dependent)mqQueue: queue/topic name
Audit fields:
createdAt: creation timeupdatedAt: last updated time
Create and update Trigger
- Create:
POST /linker/workflows/<workflowId>/triggersbinds a trigger to the given workflow. - Update:
PUT /linker/triggers/<triggerId>partially updates fields (only fields in request body are changed).
Type-specific update behaviors:
- For
WEBHOOK:- You can update
webhookSecret,webhookSignatureHeader,webhookTimestampHeader,webhookMaxSkewSeconds.
- You can update
- For
CRON:- Updating
cronExpressionorcronTimezoneresetscronNextFireAtto be recalculated by the scheduler.
- Updating
- For
MQ:- You can update
mqDriver,mqQueue.
- You can update
Example: update a Webhook trigger’s signing config:
curl -X PUT "${API_BASE}/linker/triggers/<triggerId>" \
-H "Authorization: Bearer ${TOKEN}" \
-H "X-Tenant-Id: <tenantId>" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Webhook",
"enabled": true,
"webhookSecret": "my-signing-secret",
"webhookSignatureHeader": "X-Signature",
"webhookTimestampHeader": "X-Timestamp",
"webhookMaxSkewSeconds": 300
}'
Webhook triggering and signature verification
Trigger endpoint:
POST /linker/triggers/webhook/<triggerId>
Request body:
- arbitrary payload, most commonly
application/json
Signature and security (see TriggerController#webhook and verifyWebhookSignature):
- If
webhookSecretis not configured, signature is not verified (for internal/test environments). - If
webhookSecretis configured:-
Timestamp header: default
X-Timestamp(or configuredwebhookTimestampHeader) -
Signature header: default
X-Signature(or configuredwebhookSignatureHeader) -
Timestamp is a Unix second timestamp (
Instant.now().getEpochSecond()). -
Allowed skew is
webhookMaxSkewSeconds; requests beyond that are rejected. -
Signed payload:
<timestamp>.<body-as-utf8-string> -
Signature:
HMAC-SHA256(secret, signedPayload)encoded as lowercase hex. -
Request header supports a prefix form
sha256=<hex>; the prefix is stripped before comparison. -
Constant-time comparison is used to avoid timing attacks.
-
Client pseudo-code:
TIMESTAMP=$(date +%s)
BODY='{"event":"order.created","id":"evt_123","amount":100}'
SIGNED_PAYLOAD="${TIMESTAMP}.${BODY}"
SIGNATURE_HEX=$(echo -n "$SIGNED_PAYLOAD" | \
openssl dgst -sha256 -hmac "my-signing-secret" | \
sed 's/^.* //')
curl -X POST "${API_BASE}/linker/triggers/webhook/<triggerId>" \
-H "Content-Type: application/json" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Signature: sha256=${SIGNATURE_HEX}" \
-d "$BODY"
Event idempotency and EventKey
The webhook event idempotency key eventKey is determined (see resolveEventKey) by:
- Prefer headers in this order:
X-Event-IdX-Request-IdX-Idempotency-Key
- The first non-empty value becomes
eventKey; if none exist,eventKeyis the SHA-256 hash of the request body.
Within the same Trigger, events sharing the same eventKey are treated as duplicates and return duplicate = true, avoiding double-processing.
Webhook payload parsing
Payload parsing (see parsePayload):
-
If body is empty, the trigger sees
{}(empty object). -
If
Content-Typecontainsapplication/jsonand JSON parsing succeeds:- payload is that JSON object and passed through to the workflow.
-
Otherwise:
{
"raw": "<UTF-8 decoded text>",
"base64": "<Base64 of raw bytes>"
}
Webhook response structure
Webhook triggers return:
{
"success": true,
"data": {
"accepted": true,
"duplicate": false,
"eventInboxId": "inbox_123",
"workflowExecutionId": "exec_456"
}
}
accepted: whether the event was written to the inbox and entered scheduling.duplicate: whether aneventKeyduplicate was detected.eventInboxId: ID of event inbox record.workflowExecutionId: workflow execution ID (if started).
Event inbox (TriggerEventInbox)
View events for a given Trigger:
- GET
/linker/triggers/<triggerId>/events?page=&size=
Response fields (see TriggerEventSummary):
items: event list, each with:id: event IDeventKey: idempotency keystatus: processing status (PENDING/RUNNING/DONE/FAILED, etc.)attempts: retry countworkflowExecutionId: bound workflow execution IDlastError: last error message (if any)payloadHash: payload hash (for troubleshooting)createdAt/updatedAt: timestamps
page: current page (starting from 0)size: page sizetotal: total count
Task execution and human approvals (/api/task)
- Run: POST
/linker/task/run - Resume: POST
/linker/task/resume - Pending human tasks: GET
/linker/task/human/pending - Complete approval: POST
/linker/task/human/<taskId>/complete - Report: GET
/linker/task/report?taskID=<executionId> - Execution list: GET
/linker/task/executions?tenantId=&workflowId=&status=&userId=&startTime=&endTime=&page=&size=
End-to-end execution flow
- Linker admin console (visual) creates and publishes workflows.
- Configure Triggers (Webhook / Cron / MQ) for workflows.
- External systems call gateway
/linker/...endpoints to trigger workflows. - Use
/linker/task/human/...APIs to drive approval nodes. - Use
/linker/task/reportand/linker/task/executionsfor runtime monitoring and troubleshooting.