API Data Exchange Schemas
Synced from the source repositories. Do not edit by hand.
Last Updated: Nov 19, 2024 Purpose: Document all API data structures to avoid debugging time on format mismatches
This document captures the exact data structures used between frontend (React) and backend (Strapi/Python) to prevent common issues like double-wrapped responses, missing fields, and type mismatches.
Table of Contents
- Workflow Definition Schema
- API Response Formats
- Node Configuration
- Workflow Execution Flow
- Common Pitfalls
Workflow Definition Schema
Frontend Format (React Flow)
What the UI uses:
{
nodes: [
{
id: "node-123",
type: "email_send_task", // Actual task type as node type
position: { x: 100, y: 200 },
data: {
label: "Send Email",
task_type: "email_send_task", // Also in data
config: {
to: ["user@example.com"], // MUST be array!
subject: "Hello {{name}}",
body: "Message body"
}
}
}
],
edges: [
{
id: "edge-1",
source: "node-1",
target: "node-2"
}
]
}Backend Format (Processor Expected)
What the processor needs:
{
nodes: [
{
id: "node-123",
type: "task", // ⚠️ MUST be "task", not the actual task type!
position: { x: 100, y: 200 },
data: {
label: "Send Email",
task_type: "email_send_task", // Actual task type here
config: {
to: ["user@example.com"],
subject: "Hello {{name}}",
body: "Message body"
}
}
}
],
edges: [...],
input_schema: { // ⚠️ REQUIRED!
type: "object",
properties: {},
required: []
}
}Key Differences:
- Frontend:
node.type = "email_send_task"(specific type) - Backend:
node.type = "task"(generic),node.data.task_type = "email_send_task"(specific) - Backend requires
input_schemafield - Processor filters nodes by
type === "task"and ignores all other types
Transformation Function:
const transformNodesToBackendFormat = (nodes: Node[]) => {
return nodes.map(node => ({
...node,
type: 'task', // Convert to generic "task" type
data: {
...node.data,
task_type: node.data.task_type, // Keep specific type in data
}
}));
};API Response Formats
1. Task Types API
Endpoint: GET /api/workflows/task-types
Raw Backend Response:
{
"data": {
"task_types": [
{
"id": "email_send_task",
"name": "Send Email",
"category": "Communication",
"config_schema": {
"type": "object",
"properties": {
"to": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
]
}
}Frontend Extraction:
// In api.ts
return {
data: response.data.data.task_types || []
};What Frontend Receives:
const taskTypes = await workflowBuilderApi.getTaskTypes();
// taskTypes.data = array of task types2. Validation API
Endpoint: POST /api/workflows/validate
Request:
{
"workflow_definition": {
"nodes": [...],
"edges": [...],
"input_schema": { // ⚠️ Include this!
"type": "object",
"properties": {},
"required": []
}
}
}Raw Backend Response:
{
"data": {
"valid": true,
"errors": []
}
}Frontend Extraction:
// In api.ts
return response.data.data || response.data;
// Unwraps nested data structureWhat Frontend Receives:
const validation = await workflowBuilderApi.validate(workflowDef);
// validation = { valid: true, errors: [] }
// NOT { data: { valid: true, errors: [] } }3. Workflow Execution Flow
Step 1: Create Activity (Workflow Template)
Endpoint: POST /api/activities
Request:
{
"data": {
"name": "My Workflow",
"description": "Test workflow",
"workflow_type": "sequential",
"status": "draft",
"workflow_definition": {
"nodes": [...], // Frontend format OK here
"edges": [...],
"input_schema": {
"type": "object",
"properties": {},
"required": []
}
}
}
}Response:
{
"data": {
"id": 42,
"name": "My Workflow",
...
}
}Step 2: Create Execution
Endpoint: POST /api/workflow-executions
Request:
{
"data": {
"activity": 42, // Activity ID from step 1
"workflow_definition": {
"nodes": [...], // ⚠️ MUST use backend format (type: "task")
"edges": [...],
"input_schema": {
"type": "object",
"properties": {},
"required": []
}
},
"inputs": {}
}
}Response:
{
"data": {
"id": 93, // Execution ID
"status": "queued",
...
}
}Step 3: Execute
Endpoint: POST /api/workflow-executions/:id/execute
Request:
{
"inputs": {}
}Response:
{
"data": {
"id": 93,
"status": "processing",
...
}
}Step 4: Poll Status
Endpoint: GET /api/workflow-executions/:id/status
Response:
{
"data": {
"id": 93,
"status": "completed", // or "processing", "failed"
"outputs": {
"node-123": {
"sent": true,
"recipients": ["user@example.com"],
"timestamp": "2025-11-19T11:28:44.978Z"
}
},
"error": null,
"started_at": "2025-11-19T11:28:42.000Z",
"completed_at": "2025-11-19T11:28:44.995Z"
}
}Node Configuration
Array Fields (e.g., Email Recipients)
❌ Wrong (String):
{
"to": "user@example.com" // Will be counted as 17 characters!
}✅ Correct (Array):
{
"to": ["user@example.com"] // Array with 1 item
}Frontend Handling:
// ConfigPanel.tsx
if (fieldSchema.type === 'array' && fieldSchema.items?.type === 'string') {
// Render as comma-separated input
const arrayValue = Array.isArray(value) ? value.join(', ') : value;
onChange={(e) => {
// Convert "email1@test.com, email2@test.com" to array
const arrayValue = e.target.value
.split(',')
.map(item => item.trim())
.filter(item => item.length > 0);
handleConfigChange(fieldName, arrayValue);
}}
}EmailSendNode Display:
// Safely handle both string and array
const recipients = data.config?.to;
const recipientCount = Array.isArray(recipients) ? recipients.length : 0;Template Variables
Syntax: {{variable_name}}
Example Config:
{
"subject": "Hello {{first_name}}",
"body": "Your order {{order_id}} is {{status}}"
}Inputs:
{
"first_name": "John",
"order_id": "12345",
"status": "shipped"
}Rendered Result:
Subject: Hello John
Body: Your order 12345 is shippedRendering Logic (Backend - DMS):
const renderTemplate = (template, inputs) => {
if (!template) return template;
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const value = inputs[key.trim()];
return value !== undefined ? value : match; // Keep {{var}} if not found
});
};Workflow Execution Flow
Complete End-to-End Flow
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React) │
├─────────────────────────────────────────────────────────────┤
│ 1. User creates workflow in UI │
│ - Adds nodes (type: "email_send_task") │
│ - Configures nodes │
│ │
│ 2. User clicks "Save" │
│ → POST /api/activities │
│ → Creates Activity (workflow template) │
│ → Returns activity_id: 42 │
│ │
│ 3. User clicks "Execute" │
│ a) Transform nodes (type → "task") │
│ b) POST /api/workflow-executions │
│ → Creates WorkflowExecution │
│ → Returns execution_id: 93 │
│ │
│ c) POST /api/workflow-executions/93/execute │
│ → Queues workflow to router │
│ │
│ d) Poll GET /api/workflow-executions/93/status │
│ → Every 1 second │
│ → Until status = "completed" or "failed" │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ BACKEND (Strapi DMS) │
├─────────────────────────────────────────────────────────────┤
│ 4. Execute endpoint receives request │
│ → Calls queueWorkflow service │
│ → Builds router payload: │
│ { │
│ job_id: "wf_93", │
│ job_type: "task_based_workflow", │
│ payload: { │
│ workflow_definition: {...}, │
│ inputs: {...} │
│ }, │
│ token: "jwt_token" │
│ } │
│ → POST http://localhost:9001/api/v1/jobs │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ ROUTER (Python FastAPI) │
├─────────────────────────────────────────────────────────────┤
│ 5. Router receives job │
│ → Validates job structure │
│ → Enqueues to workflow_queue │
│ → Returns 200 OK │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PROCESSOR (Python Worker) │
├─────────────────────────────────────────────────────────────┤
│ 6. Processor picks up job from queue │
│ → Filters nodes: type === "task" │
│ → Converts to steps │
│ → For each step: │
│ a) Get processor (email_send_task) │
│ b) Execute EmailTaskProcessor │
│ → HTTP POST http://localhost:1338/api/email-task/execute
│ │
│ 7. Workflow completes │
│ → Publishes results to results_queue │
│ → Updates DMS execution status │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ RESULTS WORKER (Python Background) │
├─────────────────────────────────────────────────────────────┤
│ 8. Results worker picks up from results_queue │
│ → PUT /api/workflow-executions/internal/93/results │
│ → Updates execution: │
│ - status: "completed" │
│ - outputs: {...} │
│ - completed_at: timestamp │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (Polling) │
├─────────────────────────────────────────────────────────────┤
│ 9. Status poll detects "completed" │
│ → Shows "Workflow Completed" toast │
│ → Stops polling │
└─────────────────────────────────────────────────────────────┘Common Pitfalls
1. ❌ Double-Wrapped Data
Problem:
// Backend returns:
{ "data": { "task_types": [...] } }
// Frontend tries to access:
response.data.task_types // ❌ undefined!Solution:
// Extract in API client:
return {
data: response.data.data.task_types || []
};2. ❌ Missing input_schema
Problem:
{
"nodes": [...],
"edges": [...]
// Missing input_schema!
}Error:
"Workflow definition does not include an input schema"Solution:
{
"nodes": [...],
"edges": [...],
"input_schema": { // ✅ Always include!
"type": "object",
"properties": {},
"required": []
}
}3. ❌ Wrong Node Type
Problem:
{
"nodes": [
{
"type": "email_send_task", // ❌ Processor skips this!
...
}
]
}Processor logs:
"Executing workflow with 0 steps" // All nodes filtered out!Solution:
{
"nodes": [
{
"type": "task", // ✅ Generic type
"data": {
"task_type": "email_send_task" // ✅ Specific type here
}
}
]
}4. ❌ String Instead of Array
Problem:
{
"to": "user@example.com" // ❌ String, not array
}UI shows:
"29 recipients" // Counts string characters!Solution:
{
"to": ["user@example.com"] // ✅ Array
}5. ❌ Validation Shows Errors But valid=true
Problem:
{
"data": {
"valid": true,
"errors": []
}
}Frontend receives:
validation.data.valid // ❌ Accessing nested dataSolution:
// Unwrap in API client:
return response.data.data || response.data;
// Now:
validation.valid // ✅ Direct accessTask Type Field Schema
Email Send Task
{
id: "email_send_task",
name: "Send Email",
category: "Communication",
icon: "Mail",
processor: "email_send",
config_schema: {
type: "object",
properties: {
to: {
type: "array", // ⚠️ MUST handle as array in UI
items: { type: "string" },
description: "Recipient email addresses",
minItems: 1
},
cc: {
type: "array",
items: { type: "string" },
description: "CC recipients (optional)"
},
bcc: {
type: "array",
items: { type: "string" },
description: "BCC recipients (optional)"
},
subject: {
type: "string",
description: "Email subject (supports {{variable}} templates)"
},
body: {
type: "string",
description: "Email body (supports {{variable}} templates)"
},
html: {
type: "boolean",
default: true,
description: "Send as HTML email"
}
},
required: ["to", "subject", "body"]
},
input_schema: {
type: "object",
description: "Variables available for template replacement",
additionalProperties: true
},
output_schema: {
type: "object",
properties: {
sent: { type: "boolean" },
recipients: {
type: "array",
items: { type: "string" }
},
timestamp: { type: "string" }
}
}
}Quick Reference Card
When Creating Workflow Definition
✅ DO:
- Include input_schema: { type: "object", properties: {}, required: [] }
- Transform nodes to type: "task" before execution
- Use arrays for multi-value fields: to: ["email1", "email2"]
- Unwrap API responses: response.data.data
❌ DON'T:
- Send nodes with type: "email_send_task" to processor
- Forget input_schema (causes 400 error)
- Use strings for array fields: to: "email@test.com"
- Access nested data directly: validation.data.validWhen Adding New Task Types
- Backend adds to task registry
- Backend returns in
/api/workflows/task-types - Frontend automatically gets it (no code changes!)
- Optional: Create custom node component for better UX
- Always specify if fields are arrays in config_schema
Testing Checklist
- Validation passes with input_schema
- Array fields render as comma-separated inputs
- Node type transforms to "task" before execution
- API responses unwrap correctly
- Template variables render correctly
- Status polling shows completion
- Email/output received successfully
Version: 1.0 Authors: TurfAI Team Related Docs:
PROGRESS_TRACKER.md- Sprint progressDECISIONS_LOG.md- Architectural decisionsWORKFLOW_INTEGRATION_GUIDE.md- API reference