| name | interactor-workflows |
| description | Build state-machine based automation with human-in-the-loop support through Interactor. Use when implementing approval flows, multi-step processes, automated pipelines, or any workflow requiring user input at specific stages. |
| author | Interactor Integration Guide |
Interactor Workflows Skill
Build state-machine based automations with human-in-the-loop support for multi-step business processes.
When to Use
- Approval Flows: Multi-level approval processes (expense reports, purchase orders)
- Onboarding Workflows: Step-by-step user or employee onboarding
- Order Processing: Order fulfillment with status tracking
- Support Escalation: Ticket routing with human handoffs
- Document Processing: Review and approval pipelines
- Any Multi-Step Process: Processes requiring conditional logic and user input
Prerequisites
- Interactor authentication configured (see
interactor-auth skill)
- Understanding of state machines and workflow concepts
- Webhook endpoint for workflow notifications (recommended)
Overview
Workflows consist of:
| Component | Description |
|---|
| States | Steps in your process (action, halting, terminal) |
| Transitions | Rules for moving between states |
| Instances | Running executions of a workflow |
| Threads | Parallel execution paths within an instance |
Instructions
Step 1: Create a Workflow Definition
curl -X POST https://core.interactor.com/api/v1/workflows \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "approval_workflow",
"initial_state": "request",
"ai_guidance": "This workflow handles approval requests. Route based on amount thresholds.",
"states": {
"request": {
"type": "action",
"logic": {
"type": "script",
"code": "return { request_id: input.id, amount: input.amount, status: \"pending\", submitted_at: new Date().toISOString() }"
},
"transitions": [
{ "target": "await_approval" }
]
},
"await_approval": {
"type": "halting",
"presentation": {
"type": "form",
"title": "Approval Required",
"description": "Please review and approve or reject this request.",
"fields": [
{ "name": "approved", "type": "boolean", "label": "Approve this request?" },
{ "name": "comment", "type": "string", "label": "Comment (optional)", "multiline": true }
]
},
"transitions": [
{ "target": "approved", "condition": { "field": "approved", "equals": true } },
{ "target": "rejected" }
]
},
"approved": {
"type": "terminal",
"on_enter": {
"type": "http",
"method": "POST",
"url": "https://yourapp.com/api/webhooks/approval-complete",
"body": { "request_id": "${workflow_data.request_id}", "status": "approved" }
}
},
"rejected": {
"type": "terminal",
"on_enter": {
"type": "http",
"method": "POST",
"url": "https://yourapp.com/api/webhooks/approval-complete",
"body": { "request_id": "${workflow_data.request_id}", "status": "rejected" }
}
}
}
}'
Response:
{
"data": {
"name": "approval_workflow",
"version_id": "v_abc123",
"status": "draft",
"created_at": "2026-01-20T12:00:00Z"
}
}
State Types
| Type | Description | Behavior |
|---|
action | Executes logic automatically | Runs logic, then transitions immediately |
halting | Pauses for external input | Waits for resume call with user input |
terminal | End state | Workflow completes, no further transitions |
Note: The on_enter property shown in terminal states (for triggering HTTP callbacks on completion) is an optional enhancement. Verify availability with your Interactor version.
Step 2: Validate Without Saving
Test a workflow definition before creating it:
curl -X POST https://core.interactor.com/api/v1/workflows/validate \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "my_workflow",
"initial_state": "start",
"states": {
"start": {
"type": "action",
"logic": { "type": "script", "code": "return { message: \"Hello\" }" },
"transitions": [{ "target": "end" }]
},
"end": {
"type": "terminal"
}
}
}'
Response (success):
{
"data": {
"valid": true
}
}
Response (error):
{
"data": {
"valid": false,
"errors": [
{
"path": "states.start.transitions[0].target",
"message": "Target state 'nonexistent' does not exist"
}
]
}
}
Step 3: List Workflows
curl https://core.interactor.com/api/v1/workflows \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"workflows": [
{
"name": "approval_workflow",
"latest_version_id": "v_abc123",
"published_version_id": "v_abc123",
"created_at": "2026-01-20T12:00:00Z"
},
{
"name": "onboarding_workflow",
"latest_version_id": "v_def456",
"published_version_id": null,
"created_at": "2026-01-19T10:00:00Z"
}
]
}
}
Step 4: List Versions
curl https://core.interactor.com/api/v1/workflows/approval_workflow/versions \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"versions": [
{
"version_id": "v_abc123",
"status": "draft",
"created_at": "2026-01-20T12:00:00Z"
},
{
"version_id": "v_def456",
"status": "published",
"created_at": "2026-01-19T10:00:00Z"
}
]
}
}
Step 5: Publish a Version
Workflows must be published before they can be executed:
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/versions/v_abc123/publish \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"version_id": "v_abc123",
"status": "published",
"published_at": "2026-01-20T12:05:00Z"
}
}
Workflow Instances
Instances are running executions of a workflow.
Create Instance
Start a new workflow execution:
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/instances \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"namespace": "user_123",
"input": {
"id": "req_456",
"amount": 5000,
"requester": "john@example.com",
"description": "New laptop for development"
}
}'
Response:
{
"data": {
"id": "inst_xyz",
"workflow_name": "approval_workflow",
"version_id": "v_abc123",
"namespace": "user_123",
"status": "halted",
"current_state": "await_approval",
"workflow_data": {
"request_id": "req_456",
"amount": 5000,
"status": "pending",
"submitted_at": "2026-01-20T12:00:00Z"
},
"created_at": "2026-01-20T12:00:00Z"
}
}
Instance Status Values
| Status | Description |
|---|
running | Actively executing (in an action state) |
halted | Paused, waiting for external input |
completed | Finished successfully (reached terminal state) |
failed | Terminated due to error |
cancelled | Manually cancelled |
List Instances
curl https://core.interactor.com/api/v1/workflows/instances \
-H "Authorization: Bearer <token>"
Query Parameters:
| Parameter | Type | Description |
|---|
namespace | string | Filter by namespace |
workflow_name | string | Filter by workflow |
status | string | running, halted, completed, failed, cancelled |
Example - List halted instances for a user:
curl "https://core.interactor.com/api/v1/workflows/instances?namespace=user_123&status=halted" \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"instances": [
{
"id": "inst_xyz",
"workflow_name": "approval_workflow",
"status": "halted",
"current_state": "await_approval",
"created_at": "2026-01-20T12:00:00Z"
},
{
"id": "inst_abc",
"workflow_name": "onboarding_workflow",
"status": "halted",
"current_state": "verify_email",
"created_at": "2026-01-19T15:30:00Z"
}
]
}
}
Get Instance
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"id": "inst_xyz",
"workflow_name": "approval_workflow",
"version_id": "v_abc123",
"namespace": "user_123",
"status": "halted",
"current_state": "await_approval",
"workflow_data": {
"request_id": "req_456",
"amount": 5000,
"status": "pending"
},
"halting_presentation": {
"type": "form",
"title": "Approval Required",
"description":
Resuming Workflows
When a workflow reaches a halting state, it waits for external input.
Resume with Input
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/resume \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"input": {
"approved": true,
"comment": "Looks good, approved for Q1 budget"
}
}'
Response:
{
"data": {
"id": "inst_xyz",
"status": "completed",
"current_state": "approved",
"workflow_data": {
"request_id": "req_456",
"amount": 5000,
"status": "pending",
"approved": true,
"comment": "Looks good, approved for Q1 budget"
}
}
}
The workflow continues execution based on the input and transition conditions.
Cancel Instance
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/cancel \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"id": "inst_xyz",
"status": "cancelled",
"cancelled_at": "2026-01-20T12:30:00Z"
}
}
Threads
Workflows can have parallel execution paths (threads).
List Threads
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/threads \
-H "Authorization: Bearer <token>"
Response:
{
"data": {
"threads": [
{
"id": "thread_main",
"status": "halted",
"current_state": "await_approval"
},
{
"id": "thread_finance",
"status": "completed",
"current_state": "finance_approved"
}
]
}
}
Resume Specific Thread
For workflows with multiple parallel threads:
curl -X POST https://core.interactor.com/api/v1/workflows/instances/inst_xyz/threads/thread_1/resume \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"input": {
"department_approved": true
}
}'
Response:
{
"data": {
"id": "inst_xyz",
"status": "running",
"threads": [
{
"id": "thread_1",
"status": "completed",
"current_state": "department_approved"
},
{
"id": "thread_2",
"status": "halted",
"current_state": "await_finance_approval"
}
]
}
}
History API
Query workflow execution history for debugging, monitoring, and audit.
List History Events
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/history \
-H "Authorization: Bearer <token>"
Query Parameters:
| Parameter | Type | Description |
|---|
limit | integer | Max events (default: 100, max: 1000) |
cursor | string | Pagination cursor |
types | string | Filter by type: transition, step, halt, error, lifecycle |
since | ISO8601 | Events after this timestamp |
until | ISO8601 | Events before this timestamp |
thread | string | Filter to specific thread |
include_data | boolean | Include workflow_data snapshots |
Response:
{
"data": {
"instance_id": "inst_xyz",
"workflow_id": "wf_abc",
"status": "completed",
"events": [
{
"id": "evt_01HX...",
"type": "lifecycle",
"subtype": "created",
"timestamp": "2026-01-20T12:00:00Z",
"initial_state": "request"
},
{
"id": "evt_01HX...",
"type": "transition",
"subtype": "state_change",
"from_state": "request"
Get Single Event
curl https://core.interactor.com/api/v1/workflows/instances/inst_xyz/events/evt_01HX... \
-H "Authorization: Bearer <token>"
Add ?include_data=true to include the workflow_data snapshot at that point.
Error Dashboard
Query errors across all workflows:
curl "https://core.interactor.com/api/v1/workflows/errors?since=2026-01-20T00:00:00Z" \
-H "Authorization: Bearer <token>"
Halting Instructions
When a workflow halts, you can configure how the halting message is generated and presented to users.
AI-Generated Instructions
Use AI to dynamically generate contextual messages based on workflow data:
{
"await_approval": {
"type": "halting",
"halting_instructions": {
"type": "ai",
"config": {
"prompt": "Summarize this order and ask the user to approve or reject it.",
"model": "claude-3-haiku-20240307",
"include_data_paths": ["order", "customer", "risk_score"]
}
},
"transition_mode": "selection",
"transitions": [
{"key": "approve", "to": "approved", "description":
Simple format - treats instruction as an AI prompt:
{
"halting_instructions": {
"instruction": "Tell the user the strategy is ready for review. Highlight key metrics and risks.",
"include_data": ["strategy", "benchmarks", "risk_assessment"]
}
}
Static Message Instructions
For static messages without AI generation:
{
"halting_instructions": {
"type": "message",
"config": {
"title": "Approval Required",
"message": "This order exceeds the automatic approval threshold and requires manual review."
}
}
}
Halted Response
When halted, the API response includes halted_options:
{
"status": "halted",
"halted_at_state": "await_approval",
"halted_options": {
"instruction": "Order #123 for $150.00 from Acme Corp is ready. Risk score: Low (23).",
"include_data": ["order", "customer"],
"transition_mode": "selection",
"choices": [
{"key": "approve", "description": "Approve the order", "to": "approved"},
{"key": "reject", "description": "Reject the order", "to": "rejected"
| Field | Description |
|---|
instruction | Message to display (AI-generated or static) |
generated | true if AI-generated, false if static |
choices | Available transitions for selection mode |
Halting Presentations (Legacy)
Note: The presentation format is still supported for backward compatibility. New workflows should use halting_instructions above.
When a workflow halts, specify how to present the required input to users.
Note: The title and description fields shown in presentations are optional enhancements for better UX. The core API requires only type and the type-specific fields (fields, options, or message).
Form Presentation
{
"type": "form",
"title": "Approval Required",
"description": "Please review the request details and provide your decision.",
"fields": [
{
"name": "approved",
"type": "boolean",
"label": "Approve this request?",
"required": true
},
{
"name": "amount",
"type": "number",
"label": "Approved Amount",
"default": "${workflow_data.amount}",
"min": 0,
"max"
Choice Presentation
{
"type": "choice",
"title": "Select Action",
"message": "How would you like to proceed with this request?",
"options": [
{
"value": "approve",
"label": "Approve",
"description": "Approve the request as submitted"
},
{
"value": "reject",
"label": "Reject",
"description": "Reject the request"
},
{
"value": "escalate",
"label": "Escalate to Manager",
"description": "Send to manager for review"
Message Presentation
{
"type": "message",
"title": "Processing",
"message": "Waiting for external system response. This may take a few minutes.",
"show_progress": true
}
Note: The show_progress field is an optional UI hint. Client implementations may ignore it if not supported.
Field Types
| Type | Description | Additional Properties |
|---|
string | Text input | multiline, placeholder, maxLength |
number | Numeric input | min, max, step |
boolean | Checkbox/toggle | - |
select | Dropdown selection | options array |
date | Date picker | minDate, maxDate |
file | File upload | accept, maxSize |
Note: Common field properties include required, default, and label. Additional properties like placeholder, step, maxLength may vary by Interactor version. Test with /validate endpoint to confirm supported properties.
Workflow Logic
Script Logic
Execute JavaScript code in action states:
{
"type": "script",
"code": "const total = input.items.reduce((sum, item) => sum + item.price, 0); const needsApproval = total > 1000; return { ...workflow_data, total, needs_approval: needsApproval, calculated_at: new Date().toISOString() };"
}
Available Variables:
input - The input provided when starting or resuming the workflow
workflow_data - Current accumulated workflow data
context - Additional context (namespace, instance_id, etc.)
HTTP Logic
Make external API calls:
{
"type": "http",
"method": "POST",
"url": "https://api.yourservice.com/process",
"headers": {
"Authorization": "Bearer ${secrets.API_KEY}",
"Content-Type": "application/json"
},
"body": {
"order_id": "${workflow_data.order_id}",
"amount": "${workflow_data.amount}",
"customer_email": "${workflow_data.customer_email}"
},
"timeout": 30000,
"retry": {
"attempts": 3,
"backoff": "exponential"
}
Note: The timeout and retry properties are optional enhancements. The core API requires only type, method, url, and optionally headers and body.
Transition Conditions
Define conditions for state transitions:
{
"transitions": [
{
"target": "high_value_approval",
"condition": {
"field": "amount",
"operator": "gt",
"value": 10000
}
},
{
"target": "manager_approval",
"condition": {
"field": "amount",
"operator": "gt",
"value": 1000
}
},
{
"target": "auto_approve"
}
]
}
Operators:
| Operator | Description | Example |
|---|
equals | Exact match | { "field": "status", "equals": "approved" } |
not_equals | Not equal | { "field": "status", "not_equals": "rejected" } |
gt | Greater than | { "field": "amount", "operator": "gt", "value": 1000 } |
gte | Greater than or equal | { "field": "amount", "operator": "gte", "value": 1000 } |
lt | Less than | { "field": "amount", "operator": "lt", "value": 100 } |
lte | Less than or equal | { "field": "amount", "operator": "lte", "value": 100 } |
contains | String contains | { "field": "email", "operator": "contains", "value": "@company.com" } |
in | Value in array | { "field": "category", "operator": "in", "value": ["A", "B", "C"] } |
Complex Conditions
Use and / or for complex conditions:
{
"transitions": [
{
"target": "vp_approval",
"condition": {
"and": [
{ "field": "approved", "equals": true },
{ "field": "amount", "operator": "gt", "value": 10000 }
]
}
},
{
"target": "approved",
"condition": {
"or": [
{ "field": "amount"
Complete Example: Multi-Level Approval
{
"name": "purchase_approval",
"initial_state": "submit",
"ai_guidance": "Multi-level purchase approval workflow. Amount thresholds: <$1000 auto-approve, $1000-$10000 manager, >$10000 VP required.",
"states": {
"submit": {
"type": "action",
"logic": {
"type": "script",
"code": "return { ...input, submitted_at: new Date().toISOString(), status: 'pending' }"
},
"transitions": [
{
"target": "auto_approved",
"condition": { "field": "amount", "operator": "lte",
Implementation Examples
Elixir Implementation (Phoenix)
Prerequisite: This module requires the MyApp.Interactor.Client module from the interactor-auth skill. See that skill for the HTTP client implementation.
defmodule MyApp.Interactor.Workflows do
@moduledoc """
Interactor Workflow management for state-machine based automations.
Requires MyApp.Interactor.Client from interactor-auth skill.
"""
alias MyApp.Interactor.Client
# ============ Workflow Definitions ============
@doc """
Create a new workflow definition.
"""
def create_workflow(definition) do
Client.post("/workflows", definition)
end
@doc """
Validate a workflow definition without saving.
"""
def validate_workflow(definition) do
Client.post("/workflows/validate", definition)
end
@doc """
List all workflows.
"""
def list_workflows do
case Client.get("/workflows") do
{:ok, %{"workflows" => workflows}} -> {:ok, workflows}
error -> error
end
end
@doc """
List versions for a workflow.
"""
def list_versions(workflow_name) do
case Client.get("/workflows/#{workflow_name}/versions") do
{:ok, %{"versions" => versions}} -> {:ok, versions}
error -> error
end
end
@doc """
Publish a workflow version.
"""
def publish_version(workflow_name, version_id) do
Client.post("/workflows/#{workflow_name}/versions/#{version_id}/publish", %{})
end
# ============ Instances ============
@doc """
Create a new workflow instance.
"""
def create_instance(workflow_name, user_id, input) do
Client.post("/workflows/#{workflow_name}/instances", %{
namespace: "user_#{user_id}",
input: input
})
end
@doc """
Get a workflow instance by ID.
"""
def get_instance(instance_id) do
Client.get("/workflows/instances/#{instance_id}")
end
@doc """
List workflow instances with optional filters.
"""
def list_instances(filters \\ %{}) do
query_params =
filters
|> Enum.map(fn
{:user_id, id} -> {"namespace", "user_#{id}"}
{:workflow_name, name} -> {"workflow_name", name}
{:status, status} -> {"status", status}
end)
|> URI.encode_query()
path = if query_params == "", do: "/workflows/instances", else: "/workflows/instances?#{query_params}"
case Client.get(path) do
{:ok, %{"instances" => instances}} -> {:ok, instances}
error -> error
end
end
@doc """
Resume a halted workflow instance with input.
"""
def resume_instance(instance_id, input) do
Client.post("/workflows/instances/#{instance_id}/resume", %{input: input})
end
@doc """
Cancel a workflow instance.
"""
def cancel_instance(instance_id) do
Client.post("/workflows/instances/#{instance_id}/cancel", %{})
end
# ============ Threads ============
@doc """
List threads for an instance.
"""
def list_threads(instance_id) do
case Client.get("/workflows/instances/#{instance_id}/threads") do
{:ok, %{"threads" => threads}} -> {:ok, threads}
error -> error
end
end
@doc """
Resume a specific thread.
"""
def resume_thread(instance_id, thread_id, input) do
Client.post(
"/workflows/instances/#{instance_id}/threads/#{thread_id}/resume",
%{input: input}
)
end
# ============ Helpers ============
@doc """
Wait for a workflow to complete or halt.
Returns {:ok, instance} when completed/halted, {:error, reason} on failure/timeout.
"""
def wait_for_completion(instance_id, opts \\ []) do
timeout_ms = Keyword.get(opts, :timeout, 300_000)
poll_interval_ms = Keyword.get(opts, :poll_interval, 2_000)
deadline = System.monotonic_time(:millisecond) + timeout_ms
do_wait_for_completion(instance_id, deadline, poll_interval_ms)
end
defp do_wait_for_completion(instance_id, deadline, poll_interval_ms) do
if System.monotonic_time(:millisecond) >= deadline do
{:error, :timeout}
else
case get_instance(instance_id) do
{:ok, %{"status" => "completed"} = instance} ->
{:ok, instance}
{:ok, %{"status" => "halted"} = instance} ->
{:ok, instance}
{:ok, %{"status" => "failed", "error" => error}} ->
{:error, {:workflow_failed, error}}
{:ok, %{"status" => "cancelled"}} ->
{:error, :cancelled}
{:ok, %{"status" => "running"}} ->
Process.sleep(poll_interval_ms)
do_wait_for_completion(instance_id, deadline, poll_interval_ms)
{:error, _} = error ->
error
end
end
end
end
Elixir Usage Example
alias MyApp.Interactor.Workflows
# Create and publish a workflow
{:ok, version} = Workflows.create_workflow(purchase_approval_definition)
{:ok, _published} = Workflows.publish_version("purchase_approval", version["version_id"])
# Start a new instance
{:ok, instance} = Workflows.create_instance(
"purchase_approval",
"user_123",
%{
id: "PO-2026-001",
amount: 5500,
requester: "john@example.com",
description: "Development laptop"
}
)
IO.puts("Workflow started: #{instance["id"]}")
IO.puts("Current state: #{instance["current_state"]}")
IO.puts("Status: #{instance["status"]}")
# Handle halted state
case instance["status"] do
"halted" ->
IO.puts("Waiting for approval...")
IO.inspect(instance["halting_presentation"], label: "Presentation")
# Simulate manager approval
{:ok, resumed} = Workflows.resume_instance(instance["id"], %{
approved: true,
comment: "Approved for Q1 budget"
})
IO.puts("New status: #{resumed["status"]}")
IO.puts("New state: #{resumed["current_state"]}")
_ ->
:ok
end
Elixir LiveView Integration
First, create a component to render workflow presentations dynamically:
defmodule MyAppWeb.WorkflowComponents do
use Phoenix.Component
@doc """
Renders a workflow form based on the halting presentation.
"""
attr :presentation, :map, required: true
attr :form, :any, required: true
def workflow_form(assigns) do
~H"""
<.form for={@form} phx-submit="submit_input" class="space-y-4">
<%= if @presentation["title"] do %>
<h2 class="text-xl font-semibold"><%= @presentation["title"] %></h2>
<% end %>
<%= if @presentation["description"] do %>
<p class="text-gray-600"><%= @presentation["description"] %></p>
<% end %>
<%= case @presentation["type"] do %>
<% "form" -> %>
<%= for field <- @presentation["fields"] || [] do %>
<.workflow_field field={field} form={@form} />
<% end %>
<% "choice" -> %>
<p class="font-medium"><%= @presentation["message"] %></p>
<div class="flex flex-wrap gap-2">
<%= for option <- @presentation["options"] || [] do %>
<button
type="submit"
name="input[choice]"
value={option["value"]}
class="px-4 py-2 bg-[#4CD964] hover:bg-[#3DBF55] text-white rounded-full"
>
<%= option["label"] %>
</button>
<% end %>
</div>
<% "message" -> %>
<p><%= @presentation["message"] %></p>
<% end %>
<%= if @presentation["type"] == "form" do %>
<button type="submit" class="px-6 py-2 bg-[#4CD964] hover:bg-[#3DBF55] text-white rounded-full">
Submit
</button>
<% end %>
</.form>
"""
end
attr :field, :map, required: true
attr :form, :any, required: true
defp workflow_field(assigns) do
~H"""
<div class="space-y-1">
<label class="block font-medium">
<%= @field["label"] %>
<%= if @field["required"], do: "*" %>
</label>
<%= case @field["type"] do %>
<% "string" -> %>
<%= if @field["multiline"] do %>
<textarea
name={"input[#{@field["name"]}]"}
class="w-full border rounded-lg p-2"
placeholder={@field["placeholder"]}
><%= @field["default"] %></textarea>
<% else %>
<input
type="text"
name={"input[#{@field["name"]}]"}
value={@field["default"]}
placeholder={@field["placeholder"]}
class="w-full border rounded-lg p-2"
/>
<% end %>
<% "number" -> %>
<input
type="number"
name={"input[#{@field["name"]}]"}
value={@field["default"]}
min={@field["min"]}
max={@field["max"]}
step={@field["step"]}
class="w-full border rounded-lg p-2"
/>
<% "boolean" -> %>
<input
type="checkbox"
name={"input[#{@field["name"]}]"}
value="true"
checked={@field["default"] == true}
class="h-5 w-5"
/>
<% "select" -> %>
<select name={"input[#{@field["name"]}]"} class="w-full border rounded-lg p-2">
<%= for option <- @field["options"] || [] do %>
<option value={option["value"]} selected={option["value"] == @field["default"]}>
<%= option["label"] %>
</option>
<% end %>
</select>
<% "date" -> %>
<input
type="date"
name={"input[#{@field["name"]}]"}
value={@field["default"]}
min={@field["minDate"]}
max={@field["maxDate"]}
class="w-full border rounded-lg p-2"
/>
<% _ -> %>
<input
type="text"
name={"input[#{@field["name"]}]"}
value={@field["default"]}
class="w-full border rounded-lg p-2"
/>
<% end %>
</div>
"""
end
end
Then import it in your LiveView:
defmodule MyAppWeb.WorkflowLive.Show do
use MyAppWeb, :live_view
import MyAppWeb.WorkflowComponents
alias MyApp.Interactor.Workflows
@impl true
def mount(%{"id" => instance_id}, _session, socket) do
if connected?(socket) do
# Subscribe to workflow updates via PubSub
Phoenix.PubSub.subscribe(MyApp.PubSub, "workflow:#{instance_id}")
end
case Workflows.get_instance(instance_id) do
{:ok, instance} ->
{:ok, assign(socket, instance: instance, form: to_form(%{}))}
{:error, _} ->
{:ok, push_navigate(socket, to: ~p"/workflows")}
end
end
@impl true
def handle_event("submit_input", %{"input" => input}, socket) do
instance_id = socket.assigns.instance["id"]
case Workflows.resume_instance(instance_id, input) do
{:ok, updated_instance} ->
{:noreply, assign(socket, instance: updated_instance)}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to resume: #{inspect(reason)}")}
end
end
@impl true
def handle_event("cancel", _params, socket) do
instance_id = socket.assigns.instance["id"]
case Workflows.cancel_instance(instance_id) do
{:ok, _} ->
{:noreply, push_navigate(socket, to: ~p"/workflows")}
{:error, reason} ->
{:noreply, put_flash(socket, :error, "Failed to cancel: #{inspect(reason)}")}
end
end
@impl true
def handle_info({:workflow_updated, instance}, socket) do
{:noreply, assign(socket, instance: instance)}
end
@impl true
def render(assigns) do
~H"""
<div class="workflow-instance">
<h1>Workflow: <%= @instance["workflow_name"] %></h1>
<p>Status: <span class={status_class(@instance["status"])}><%= @instance["status"] %></span></p>
<p>Current State: <%= @instance["current_state"] %></p>
<%= if @instance["status"] == "halted" do %>
<.workflow_form
presentation={@instance["halting_presentation"]}
form={@form}
/>
<% end %>
<%= if @instance["status"] in ["running", "halted"] do %>
<button phx-click="cancel" class="btn-secondary">Cancel Workflow</button>
<% end %>
</div>
"""
end
defp status_class("completed"), do: "text-green-600"
defp status_class("failed"), do: "text-red-600"
defp status_class("cancelled"), do: "text-gray-600"
defp status_class("halted"), do: "text-yellow-600"
defp status_class(_), do: "text-blue-600"
end
TypeScript Implementation
import { InteractorClient } from './interactor-client';
export class WorkflowManager {
private client: InteractorClient;
constructor(client: InteractorClient) {
this.client = client;
}
async createWorkflow(definition: WorkflowDefinition): Promise<WorkflowVersion> {
return this.client.request('POST', '/workflows', definition);
}
async validateWorkflow(definition: WorkflowDefinition): Promise<ValidationResult> {
return this.client.request('POST', '/workflows/validate', definition);
}
async listWorkflows(): Promise<Workflow[]> {
const result = await this.client.request<{ : [] }>(, );
result.;
}
(: ): <[]> {
result = ..<{ : [] }>(
,
);
result.;
}
(: , : ): <> {
..(
,
);
}
(
: ,
: ,
: <, >
): <> {
..(, , {
: ,
input
});
}
(: ): <> {
..(, );
}
(?: {
?: ;
?: ;
?: ;
}): <[]> {
params = ();
(filters?.) params.(, );
(filters?.) params.(, filters.);
(filters?.) params.(, filters.);
query = params.();
result = ..<{ : [] }>(
,
);
result.;
}
(
: ,
: <, >
): <> {
..(, , {
input
});
}
(: ): <> {
..(, );
}
(: ): <[]> {
result = ..<{ : [] }>(
,
);
result.;
}
(
: ,
: ,
: <, >
): <> {
..(
,
,
{ input }
);
}
(
: ,
: = ,
: =
): <> {
startTime = .();
(.() - startTime < timeoutMs) {
instance = .(instanceId);
(instance. === ) {
instance;
}
(instance. === ) {
();
}
(instance. === ) {
();
}
(instance. === ) {
instance;
}
( (resolve, pollIntervalMs));
}
();
}
}
{
: ;
: ;
?: ;
: <, >;
}
{
: | | ;
?: ;
?: ;
?: [];
?: ;
}
{
: | ;
?: ;
?: ;
?: ;
?: <, >;
?: ;
?: ;
?: { : ; : | };
}
{
: | | ;
?: ;
?: ;
?: ;
?: [];
?: [];
?: ;
}
{
: ;
: | | | | | ;
: ;
?: ;
?: ;
?: ;
?: ;
?: ;
?: ;
?: ;
?: ;
?: [];
?: ;
?: ;
?: ;
?: ;
}
{
: ;
: ;
?: ;
}
{
: ;
?: ;
}
{
?: ;
?: | | | | | | | ;
?: ;
?: ;
?: ;
?: [];
?: [];
}
{
: ;
: ;
?: ;
: ;
}
{
: ;
: | ;
: ;
?: ;
}
{
: ;
?: <{ : ; : }>;
}
= | | | | ;
{
: ;
: ;
: ;
: ;
: ;
: ;
: <, >;
?: ;
: [];
: [];
?: ;
: ;
}
{
: ;
: | | ;
: ;
}
{
: ;
: ;
?: ;
?: ;
}
Usage Example
const workflowManager = new WorkflowManager(interactorClient);
const version = await workflowManager.createWorkflow(purchaseApprovalDefinition);
await workflowManager.publishVersion('purchase_approval', version.version_id);
const instance = await workflowManager.createInstance(
'purchase_approval',
'user_123',
{
id: 'PO-2026-001',
amount: 5500,
requester: 'john@example.com',
description: 'Development laptop'
}
);
console.log(`Workflow started: ${instance.id}`);
console.log(`Current state: ${instance.current_state}`);
console.log(`Status: ${instance.status}`);
if (instance.status === 'halted') {
console.log('Waiting for approval...');
console.log('Presentation:', instance.halting_presentation);
resumed = workflowManager.(instance., {
: ,
:
});
.();
.();
}
Error Handling
Workflow-Specific Errors
Common workflow errors and their resolutions:
| Error Code | HTTP Status | Description | Resolution |
|---|
workflow_not_found | 404 | Workflow definition doesn't exist | Check workflow name |
workflow_not_published | 400 | No published version available | Publish a version first |
version_not_found | 404 | Version doesn't exist | Check version ID |
instance_not_found | 404 | Instance doesn't exist | Check instance ID |
instance_not_halted | 400 | Cannot resume - not halted | Check instance status |
invalid_transition | 400 | Input doesn't match any condition | Check transition conditions |
script_error | 500 | Error executing workflow script | Check script syntax |
http_error | 500 | HTTP action failed | Check endpoint and auth |
See Also: The API Reference section contains the complete canonical error table with all endpoint-specific error codes.
Webhook Events & Subscription Management
Available Events
| Event | Description | Triggered When |
|---|
workflow.instance.created | New instance started | Instance created via API |
workflow.instance.halted | Waiting for input | Instance reaches halting state |
workflow.instance.resumed | Instance resumed | Resume called with input |
workflow.instance.completed | Finished successfully | Instance reaches terminal state |
workflow.instance.failed | Terminated with error | Script/HTTP error or invalid transition |
workflow.instance.cancelled | Manually cancelled | Cancel endpoint called |
Create Webhook Subscription
curl -X POST https://core.interactor.com/api/v1/webhooks/subscriptions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/api/webhooks/workflows",
"events": [
"workflow.instance.halted",
"workflow.instance.completed",
"workflow.instance.failed"
],
"secret": "whsec_your_webhook_secret_min_32_chars",
"namespace_filter": "user_123"
}'
Response:
{
"data": {
"id": "whsub_abc123",
"url": "https://yourapp.com/api/webhooks/workflows",
"events": ["workflow.instance.halted", "workflow.instance.completed", "workflow.instance.failed"],
"namespace_filter": "user_123",
"status": "active",
"created_at": "2026-01-20T12:00:00Z"
}
}
List Webhook Subscriptions
curl https://core.interactor.com/api/v1/webhooks/subscriptions \
-H "Authorization: Bearer <token>"
Delete Webhook Subscription
curl -X DELETE https://core.interactor.com/api/v1/webhooks/subscriptions/whsub_abc123 \
-H "Authorization: Bearer <token>"
Webhook Delivery Headers
Every webhook delivery includes these headers:
| Header | Description | Example |
|---|
X-Interactor-Event | Event type | workflow.instance.halted |
X-Interactor-Signature | HMAC-SHA256 signature | sha256=abc123... |
X-Interactor-Delivery | Unique delivery ID | del_01F8B6XY... |
X-Interactor-Timestamp | Unix timestamp | 1705752000 |
X-Interactor-Retry-Count | Retry attempt (0-based) | 0 |
Content-Type | Always JSON | application/json |
Webhook Payload Example (workflow.instance.halted)
{
"event": "workflow.instance.halted",
"delivery_id": "del_01F8B6XY...",
"timestamp": "2026-01-20T12:00:01Z",
"data": {
"instance_id": "inst_xyz",
"workflow_name": "approval_workflow",
"version_id": "v_abc123",
"namespace": "user_123",
"current_state": "await_approval",
"workflow_data": {
"request_id": "req_456",
"amount": 5000,
"status": "pending"
},
"halting_presentation": {
"type"
Webhook Payload Example (workflow.instance.completed)
{
"event": "workflow.instance.completed",
"delivery_id": "del_02G9C7ZW...",
"timestamp": "2026-01-20T12:05:00Z",
"data": {
"instance_id": "inst_xyz",
"workflow_name": "approval_workflow",
"version_id": "v_abc123",
"namespace": "user_123",
"current_state": "approved",
"workflow_data": {
"request_id": "req_456",
"amount": 5000,
"status": "approved",
"approved_at": "2026-01-20T12:05:00Z"
}
}
Webhook Signature Verification
Always verify webhook signatures to ensure requests are from Interactor.
Node.js/TypeScript:
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string | Buffer,
signature: string,
secret: string,
timestamp: string
): boolean {
const timestampAge = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (timestampAge > 300) {
return false;
}
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}
app.(, express.({ : }), {
signature = req.[] ;
timestamp = req.[] ;
(!(req., signature, process..!, timestamp)) {
res.().({ : });
}
event = .(req..());
res.().({ : });
});
Elixir/Phoenix:
defmodule MyAppWeb.WebhookController do
use MyAppWeb, :controller
@webhook_secret Application.compile_env(:my_app, :webhook_secret)
@max_age_seconds 300
def handle(conn, _params) do
signature = get_req_header(conn, "x-interactor-signature") |> List.first()
timestamp = get_req_header(conn, "x-interactor-timestamp") |> List.first()
{:ok, body, conn} = read_body(conn)
case verify_signature(body, signature, timestamp) do
:ok ->
event = Jason.decode!(body)
process_event(event)
json(conn, %{received: true})
{:error, reason} ->
conn
|> put_status(401)
|> json(%{error: reason})
end
end
defp verify_signature(payload, signature, timestamp) do
with :ok <- verify_timestamp(timestamp),
:ok <- verify_hmac(payload, signature, timestamp) do
:ok
end
end
defp verify_timestamp(timestamp) do
timestamp_int = String.to_integer(timestamp)
age = System.system_time(:second) - timestamp_int
if age <= @max_age_seconds do
:ok
else
{:error, "Timestamp too old"}
end
end
defp verify_hmac(payload, signature, timestamp) do
signed_payload = "#{timestamp}.#{payload}"
expected = "sha256=" <> Base.encode16(
:crypto.mac(:hmac, :sha256, @webhook_secret, signed_payload),
case: :lower
)
if Plug.Crypto.secure_compare(expected, signature) do
:ok
else
{:error, "Invalid signature"}
end
end
defp process_event(%{"event" => "workflow.instance.halted"} = event) do
# Handle halted workflow - notify user, etc.
IO.inspect(event, label: "Workflow halted")
end
defp process_event(%{"event" => "workflow.instance.completed"} = event) do
# Handle completed workflow
IO.inspect(event, label: "Workflow completed")
end
defp process_event(event) do
IO.inspect(event, label: "Unknown event")
end
end
Webhook Retry Policy
| Attempt | Delay | Cumulative Time |
|---|
| 1 | Immediate | 0s |
| 2 | 30 seconds | 30s |
| 3 | 2 minutes | 2m 30s |
| 4 | 8 minutes | 10m 30s |
| 5 | 30 minutes | 40m 30s |
| 6 | 2 hours | 2h 40m 30s |
- Webhooks are retried up to 6 times with exponential backoff
- Return
2xx status to acknowledge receipt
- Non-2xx responses or timeouts (30s) trigger retries
- After all retries fail, the event is moved to a dead-letter queue
- Use the webhook dashboard to replay failed events
See interactor-webhooks skill for complete webhook management.
Authentication & Authorization
Required OAuth Scopes
| Scope | Description | Required For |
|---|
workflows:read | Read workflow definitions and instances | GET endpoints |
workflows:write | Create/modify workflows and instances | POST/PUT/DELETE endpoints |
workflows:execute | Create and resume instances | Instance operations |
webhooks:manage | Manage webhook subscriptions | Webhook endpoints |
Token Format
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Token Refresh
Tokens expire after 1 hour. Refresh before expiry:
curl -X POST https://auth.interactor.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "<refresh_token>",
"client_id": "<client_id>"
}'
Namespace Authorization
- Instances are isolated by
namespace
- Tokens can only access instances in namespaces they own
- Use
user_{user_id} convention for user-specific workflows
- Use
org_{org_id} convention for organization-wide workflows
- Service accounts can access all namespaces within their account
See interactor-auth skill for complete authentication setup.
Idempotency & Concurrency
Idempotency Keys
Use Idempotency-Key header to prevent duplicate operations:
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/instances \
-H "Authorization: Bearer <token>" \
-H "Idempotency-Key: order_123_approval_v1" \
-H "Content-Type: application/json" \
-d '{
"namespace": "user_123",
"input": {"order_id": "order_123", "amount": 5000}
}'
Behavior:
- If the same
Idempotency-Key is used within 24 hours, the original response is returned
- Keys are scoped to the authenticated account
- Use deterministic keys based on business identifiers (e.g.,
{order_id}_approval)
Supported Endpoints:
POST /workflows/{name}/instances (create instance)
POST /workflows/instances/{id}/resume (resume instance)
POST /workflows/instances/{id}/threads/{thread_id}/resume (resume thread)
Concurrent Resume Handling
When multiple resume requests arrive simultaneously:
| Scenario | Behavior |
|---|
| Same instance, same input | Second request returns same result (idempotent) |
| Same instance, different input | First request wins, second gets 409 Conflict |
| Different threads, same instance | Both processed (parallel execution) |
Conflict Response:
{
"error": {
"code": "concurrent_modification",
"message": "Instance was modified by another request",
"details": {
"current_state": "approved",
"expected_state": "await_approval"
},
"request_id": "req_01F8B6..."
}
}
Limits & Quotas
Workflow Definition Limits
| Limit | Value | Notes |
|---|
| Max states per workflow | 100 | Including terminal states |
| Max transitions per state | 20 | Evaluated in order |
| Max workflow name length | 64 chars | Alphanumeric, underscores, hyphens |
| Max script code size | 64 KB | Per script logic block |
| Max presentation fields | 50 | Per halting state |
| Max workflow definition size | 1 MB | Total JSON size |
Instance Limits
| Limit | Value | Notes |
|---|
Max workflow_data size | 256 KB | Accumulated across states |
| Max input payload size | 64 KB | Per resume/create call |
| Max concurrent threads | 10 | Per instance |
| Instance TTL (running) | 30 days | Auto-cancelled after |
| Instance TTL (halted) | 90 days | Auto-cancelled after |
| Max instances per namespace | 10,000 | Active instances |
File Upload Limits (for file field type)
| Limit | Value |
|---|
| Max file size | 10 MB |
| Allowed MIME types | Configurable per field |
| Max files per field | 5 |
| File retention | 7 days after instance completion |
Rate Limits
| Endpoint Category | Limit | Window |
|---|
| Read operations | 1000 req | per minute |
| Write operations | 100 req | per minute |
| Instance creation | 50 req | per minute |
| Webhook deliveries | 1000 events | per minute per subscription |
Rate Limit Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705752060
Script Execution Environment
Runtime Specification
| Property | Value |
|---|
| Runtime | JavaScript (ES2020) |
| Engine | QuickJS sandbox |
| Execution timeout | 5 seconds |
| Memory limit | 16 MB |
| Network access | Disabled (use HTTP logic instead) |
Available Globals
input
workflow_data
context
JSON
Date
Math
console
Array
Object
String
Number
Boolean
RegExp
fetch
require
eval
Function
setTimeout
setInterval
Accessing Secrets in Scripts
Secrets are accessed via the secrets object (read-only):
const apiKey = secrets.MY_API_KEY;
return { ...workflow_data, api_key_present: !!apiKey };
Security: Secrets are injected at runtime and never logged. Use HTTP logic for external calls requiring secrets.
Script Best Practices
- Keep scripts simple and fast (<100ms recommended)
- Avoid loops over large datasets
- Delegate heavy computation to HTTP endpoints
- Use
console.log sparingly (logs are stored in instance history)
- Return plain objects (no functions or circular references)
Observability & Tracing
Correlation IDs
Every API request returns a unique request ID:
X-Request-Id: req_01F8B6XY9Z...
Include this ID when contacting support or debugging issues.
Propagating Trace Context
Pass trace context to correlate across services:
curl -X POST https://core.interactor.com/api/v1/workflows/approval_workflow/instances \
-H "Authorization: Bearer <token>" \
-H "X-Request-Id: your-correlation-id-123" \
-H "traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
The traceparent header (W3C Trace Context) is propagated to HTTP logic actions.
Instance History & Logs
Each instance maintains a detailed execution history:
{
"history": [
{
"state": "request",
"entered_at": "2026-01-20T12:00:00Z",
"exited_at": "2026-01-20T12:00:01Z",
"transition": "await_approval",
"logs": ["Processing request req_456"],
"duration_ms": 45
},
{
"state": "await_approval",
"entered_at": "2026-01-20T12:00:01Z",
"input_received": {"approved": true, "comment": "LGTM"},
"resumed_at": "2026-01-20T12:05:00Z"
Metrics Available
| Metric | Description |
|---|
workflow.instance.created | Counter: instances created |
workflow.instance.completed | Counter: instances completed |
workflow.instance.failed | Counter: instances failed |
workflow.state.duration | Histogram: time in each state |
workflow.script.duration | Histogram: script execution time |
workflow.http.duration | Histogram: HTTP action duration |
Access metrics via the Interactor dashboard or export to your observability platform.
Best Practices
DO
- Start simple - Begin with linear workflows, add complexity as needed
- Use meaningful state names -
await_manager_approval over state_3
- Validate early - Use
/validate endpoint during development
- Version carefully - Publish new versions rather than modifying existing ones
- Handle all paths - Ensure every state has a valid transition or is terminal
- Use namespaces - Isolate workflow instances per user
- Add AI guidance - Help AI assistants understand your workflow's purpose
DON'T
- Don't modify published versions - Create new versions instead
- Don't create orphan states - Every state should be reachable
- Don't forget error handling - Add appropriate error states
- Don't use complex scripts - Keep logic simple, move complexity to HTTP endpoints
Output Format
When implementing workflows, provide this summary:
## Workflow Implementation Report
**Date**: YYYY-MM-DD
**Workflow**: purchase_approval
### Definition
| Property | Value |
|----------|-------|
| Name | purchase_approval |
| Version | v_abc123 |
| Status | Published |
| States | 7 |
| Halting States | 2 |
### State Flow
submit → manager_approval → [vp_approval] → approved → notify → complete
↘ rejected → notify → complete
### Implementation Checklist
- [ ] Workflow definition created
- [ ] Validation passed
- [ ] Version published
- [ ] Instance creation tested
- [ ] Resume functionality tested
- [ ] All transitions verified
- [ ] Webhook handlers configured
- [ ] Error handling implemented
### Test Scenarios
| Scenario | Input | Expected Path | Status |
|----------|-------|---------------|--------|
| Auto-approve | amount: 500 | submit → auto_approved → complete | ✓ |
| Manager only | amount: 5000 | submit → manager → approved → complete | ✓ |
| VP required | amount: 15000 | submit → manager → vp → approved → complete | ✓ |
| Rejected | amount: 5000, approved: false | submit → manager → rejected → complete | ✓ |
API Reference
Endpoint Summary
| Method | Endpoint | Auth | Success | Description |
|---|
POST | /workflows | workflows:write | 201 | Create workflow definition |
POST | /workflows/validate | workflows:read | 200 | Validate without saving |
GET | /workflows | workflows:read | 200 | List all workflows |
GET | /workflows/{name}/versions | workflows:read | 200 | List workflow versions |
POST | /workflows/{name}/versions/{id}/publish | workflows:write | 200 | Publish a version |
POST | /workflows/{name}/instances | workflows:execute | 201 | Create instance |
GET | /workflows/instances | workflows:read | 200 | List instances |
GET | /workflows/instances/{id} | workflows:read | 200 | Get instance details |
POST | /workflows/instances/{id}/resume | workflows:execute | 200 | Resume halted instance |
POST | /workflows/instances/{id}/cancel | workflows:execute | 200 |
Error Response Format
All errors follow this standardized format:
{
"error": {
"code": "workflow_not_found",
"message": "Workflow 'invalid_workflow' not found",
"details": null,
"request_id": "req_01F8B6XY9Z..."
}
}
Error Codes by Endpoint
| Endpoint | Error Code | HTTP | Description |
|---|
| All | unauthorized | 401 | Missing or invalid token |
| All | forbidden | 403 | Insufficient scopes |
| All | rate_limited | 429 | Rate limit exceeded |
| All | internal_error | 500 | Server error |
POST /workflows | invalid_workflow | 400 | Schema validation failed |
POST /workflows | workflow_exists | 409 | Name already taken |
GET /workflows/{name}/* | workflow_not_found | 404 | Workflow doesn't exist |
POST /.../publish | version_not_found | 404 | Version doesn't exist |
POST /.../publish | already_published | 400 | Version already published |
POST /.../instances | workflow_not_published | 400 | No published version |
POST /.../instances | namespace_quota_exceeded | 429 | Too many instances |
GET /instances/{id} | instance_not_found | 404 | Instance doesn't exist |
POST /.../resume | instance_not_halted | 400 | Instance not in halted state |
POST /.../resume | invalid_transition | 400 | Input doesn't match conditions |
POST /.../resume | concurrent_modification |
React Component Example
Render halting_presentation in React applications:
import React from 'react';
interface Field {
name: string;
type: 'string' | 'number' | 'boolean' | 'select' | 'date' | 'file';
label: string;
required?: boolean;
default?: any;
multiline?: boolean;
placeholder?: string;
options?: { value: string; label: string }[];
min?: number;
max?: number;
}
interface Option {
value: string;
label: string;
description?: string;
}
interface Presentation {
type: 'form' | 'choice' | 'message';
title?: string;
description?: string;
message?: string;
fields?: [];
?: [];
?: ;
}
{
: ;
: ;
?: ;
?: ;
}
() {
[formData, setFormData] = .<<, >>( {
: <, > = {};
presentation.?.( {
(field. !== ) {
defaults[field.] = field.;
}
});
defaults;
});
= () => {
e.();
(formData);
};
= () => {
({ : value });
};
= () => {
( ({ ...prev, [name]: value }));
};
(
);
}
() {
baseInputClass = ;
(
);
}
() {
[instance, setInstance] = .<>();
[isSubmitting, setIsSubmitting] = .();
.( {
()
.( res.())
.( (data.));
}, [instanceId]);
= () => {
();
{
res = (, {
: ,
: { : },
: .({ input })
});
updated = res.();
(updated.);
} {
();
}
};
(!instance) ;
(instance. !== ) ;
(
);
}
JSON Schema Appendix
Workflow Definition Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://core.interactor.com/schemas/workflow-definition.json",
"title": "Workflow Definition",
"type": "object",
"required": ["name", "initial_state", "states"],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9_-]{0,63}$",
"description": "Unique workflow identifier"
},
"initial_state": {
"type": "string",
"description": "Starting state name"