TurfAITurfAI Developers
Api_synced

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

  1. Workflow Definition Schema
  2. API Response Formats
  3. Node Configuration
  4. Workflow Execution Flow
  5. 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_schema field
  • 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 types

2. 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 structure

What 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 shipped

Rendering 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 data

Solution:

// Unwrap in API client:
return response.data.data || response.data;

// Now:
validation.valid  // ✅ Direct access

Task 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.valid

When Adding New Task Types

  1. Backend adds to task registry
  2. Backend returns in /api/workflows/task-types
  3. Frontend automatically gets it (no code changes!)
  4. Optional: Create custom node component for better UX
  5. 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 progress
  • DECISIONS_LOG.md - Architectural decisions
  • WORKFLOW_INTEGRATION_GUIDE.md - API reference

On this page