Skip to main content
FlowDrop is a frontend editor that calls your backend REST API. This guide explains what endpoints to implement, what request/response formats FlowDrop expects, and how to get a working backend running.

Endpoint Tiers

Not all endpoints are required. Here they are organized by priority:

Tier 1: Minimum Viable Backend

These 5 endpoints are the bare minimum to get FlowDrop working:
MethodPathPurpose
GET/healthHealth check (FlowDrop checks this on mount)
GET/nodesList available node types
GET/workflows/:idLoad a workflow
POST/workflowsCreate a new workflow
PUT/workflows/:idUpdate an existing workflow

Tier 2: Full Editor Experience

These endpoints enable the complete sidebar, categories, and port validation:
MethodPathPurpose
GET/categoriesNode category definitions (sidebar groups)
GET/port-configPort data types and compatibility rules
GET/nodes/:idGet a single node’s metadata
GET/workflowsList all workflows
DELETE/workflows/:idDelete a workflow

Tier 3: Advanced Features

These enable playground, execution, and interrupts:
MethodPathPurpose
POST/workflows/:id/executeExecute a workflow
GET/executions/:idGet execution status
POST/workflows/:id/playground/sessionsCreate playground session
GET/playground/sessions/:sid/messagesPoll for messages
POST/playground/sessions/:sid/messagesSend user message
GET/interrupts/:idGet pending interrupt
POST/interrupts/:idResolve an interrupt
GET/system/configRuntime configuration
The OpenAPI spec is the authoritative contract. The Tier 1–2 shapes are documented in full below, the Tier 3 shapes under Advanced endpoint formats. For the exhaustive schema, see the OpenAPI specification.

Base URL Configuration

All paths above are relative to a base URL you configure:
import { createEndpointConfig } from '@flowdrop/flowdrop/core';

const endpointConfig = createEndpointConfig('/api/flowdrop');
// Nodes endpoint becomes: GET /api/flowdrop/nodes

Request & Response Formats

GET /health

FlowDrop calls this to verify the backend is reachable. Response:
{
  "status": "ok",
  "version": "1.0.0"
}

GET /nodes

Returns all available node types. FlowDrop uses this to populate the sidebar. Query parameters:
  • category (optional) — filter by category
  • search (optional) — search name/description
  • limit (optional, default: 100)
  • offset (optional, default: 0)
Response:
{
  "success": true,
  "data": [
    {
      "id": "text_input",
      "name": "Text Input",
      "description": "Accepts text from the user",
      "type": "simple",
      "category": "inputs",
      "icon": "mdi:text-box-outline",
      "inputs": [],
      "outputs": [
        {
          "id": "output",
          "name": "Text",
          "type": "output",
          "dataType": "string"
        }
      ],
      "configSchema": {
        "type": "object",
        "properties": {
          "placeholder": {
            "type": "string",
            "title": "Placeholder",
            "default": "Enter text..."
          }
        }
      }
    }
  ]
}
Key fields in NodeMetadata:
  • id (required) — unique identifier
  • name (required) — display name
  • type — node visual type: workflowNode, simple, square, tool, gateway, terminal, idea, note
  • category — sidebar group: inputs, outputs, models, processing, logic, tools, etc.
  • iconIconify icon ID (e.g., mdi:text-box-outline)
  • inputs / outputs — port definitions with id, name, type, dataType
  • configSchema — JSON Schema defining the configuration form

POST /workflows

Creates a new workflow. FlowDrop sends the full workflow JSON. Request body:
{
  "name": "My Workflow",
  "description": "A simple workflow",
  "nodes": [
    {
      "id": "node-1",
      "type": "simple",
      "position": { "x": 100, "y": 200 },
      "data": {
        "label": "Text Input",
        "config": { "placeholder": "Enter text..." },
        "metadata": { "id": "text_input", "name": "Text Input", "...": "..." }
      }
    }
  ],
  "edges": [
    {
      "id": "edge-1",
      "source": "node-1",
      "sourceHandle": "output",
      "target": "node-2",
      "targetHandle": "input"
    }
  ]
}
Response:
{
  "success": true,
  "data": {
    "id": "wf-abc123",
    "name": "My Workflow",
    "nodes": [],
    "edges": [],
    "metadata": {
      "schemaVersion": "1.0.0",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-01T00:00:00Z"
    }
  }
}

PUT /workflows/:id

Updates an existing workflow. Same request body format as POST.

GET /workflows/:id

Returns a single workflow by ID. Same response format as POST response.

GET /categories

Returns category definitions for the node sidebar. Response:
{
  "success": true,
  "data": [
    {
      "id": "inputs",
      "name": "Inputs",
      "description": "Data input nodes",
      "icon": "mdi:import",
      "color": "var(--fd-node-emerald)",
      "weight": 10
    },
    {
      "id": "processing",
      "name": "Processing",
      "description": "Data transformation nodes",
      "icon": "mdi:cog",
      "color": "var(--fd-node-blue)",
      "weight": 30
    }
  ]
}

GET /port-config

Returns data type definitions and compatibility rules for port connections. Response:
{
  "success": true,
  "data": {
    "version": "1.0.0",
    "defaultDataType": "string",
    "dataTypes": [
      {
        "id": "string",
        "name": "String",
        "description": "Text data",
        "color": "#10b981",
        "category": "basic"
      },
      {
        "id": "json",
        "name": "JSON",
        "description": "Structured data",
        "color": "#f59e0b",
        "category": "complex"
      }
    ],
    "compatibilityRules": [
      { "from": "string", "to": "json" },
      { "from": "json", "to": "string" }
    ]
  }
}

Advanced endpoint formats (Tier 3)

These power execution, the interactive playground, and human-in-the-loop interrupts. They use the same { "success": true, "data": ... } envelope as Tier 1–2 unless noted.

POST /workflows/:id/execute

Starts a run. The body is optional. Request:
{
  "inputs": { "text_input": "Hello" },
  "options": { "timeout": 30000, "maxSteps": 50 }
}
Response (202 Accepted):
{
  "success": true,
  "data": {
    "execution_id": "exec-abc123",
    "status": "running",
    "started_at": "2025-01-01T00:00:00Z",
    "estimated_completion": "2025-01-01T00:00:05Z"
  }
}

GET /executions/:id

Poll for execution status using the execution_id returned above.
This endpoint returns the status object directly — no success/data envelope. It is the one exception to the response wrapper.
Response:
{
  "status": "completed",
  "jobs": [],
  "node_statuses": {
    "node-1": { "status": "completed" }
  },
  "job_status_summary": {}
}
status is one of pending, running, completed, failed, cancelled, paused, interrupted. The playground’s isTerminalStatus and shouldStopPolling callbacks key off these values.

POST /workflows/:id/playground/sessions

Create an isolated test session for a workflow. The body is optional. Request:
{ "name": "Test Session 1" }
Response (201):
{
  "success": true,
  "data": {
    "id": "sess-abc123",
    "workflowId": "wf-abc123",
    "name": "Test Session 1",
    "status": "idle",
    "createdAt": "2025-01-01T00:00:00Z",
    "updatedAt": "2025-01-01T00:00:00Z"
  }
}
status is one of idle, running, awaiting_input, completed, failed.

POST /playground/sessions/:sid/messages

Send a user message — this triggers a run. The message is created with status pending and processed asynchronously; poll the messages endpoint to track it. Request:
{
  "content": "Process this file",
  "inputs": { "file_path": "/data/input.csv" }
}
Response (200):
{
  "success": true,
  "data": {
    "id": "msg-1",
    "sessionId": "sess-abc123",
    "role": "user",
    "content": "Process this file",
    "status": "pending",
    "sequenceNumber": 1,
    "timestamp": "2025-01-01T00:00:00Z"
  }
}
Returns 409 if the previous message in the session is still processing — messages are handled in sequence.

GET /playground/sessions/:sid/messages

Poll for new messages. Supports since, latest, and before query parameters for pagination. Response:
{
  "success": true,
  "data": [
    {
      "id": "msg-2",
      "sessionId": "sess-abc123",
      "role": "assistant",
      "content": "Done — processed 42 rows.",
      "status": "completed",
      "sequenceNumber": 2,
      "timestamp": "2025-01-01T00:00:03Z"
    }
  ],
  "hasMore": false,
  "sessionStatus": "completed"
}
role is user, assistant, system, or log. sessionStatus tells the poller when to stop.

GET /interrupts/:id

Fetch a pending human-in-the-loop interrupt. Response:
{
  "success": true,
  "data": {
    "id": "int-abc123",
    "type": "confirmation",
    "status": "pending",
    "nodeId": "node-3",
    "executionId": "exec-abc123",
    "allowCancel": true,
    "config": {
      "message": "Do you approve this action?",
      "confirm_label": "Approve",
      "cancel_label": "Reject"
    },
    "createdAt": "2025-01-01T00:00:00Z"
  }
}
type is confirmation, choice, text, form, or review; status is pending, resolved, or cancelled.

POST /interrupts/:id

Resolve an interrupt by submitting the user’s response. The value type depends on the interrupt type. Request:
{ "value": true }
Interrupt typevalue shape
confirmationboolean
choicestring or string[]
textstring
formobject matching the form schema
reviewdecisions map + summary
Response: the updated interrupt (same shape as GET /interrupts/:id, now with status: "resolved").

GET /system/config

Public runtime configuration the editor reads on mount. Response:
{
  "success": true,
  "data": {
    "version": "1.0.0",
    "features": { "playground": true, "interrupts": true },
    "limits": { "maxWorkflowNodes": 100, "maxConcurrentExecutions": 5 }
  }
}

CORS Configuration

FlowDrop runs in the browser, so your backend must allow cross-origin requests if served from a different domain:
// Express example
import cors from 'cors';
app.use(
  cors({
    origin: 'http://localhost:5173', // your frontend URL
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization']
  })
);

Error Response Format

When an operation fails, return a consistent error format:
{
  "success": false,
  "error": "Workflow not found",
  "code": "NOT_FOUND",
  "message": "No workflow exists with ID 'wf-xyz'"
}
FlowDrop’s API client expects standard HTTP status codes:
  • 200 — success
  • 201 — created
  • 400 — bad request (validation error)
  • 401 — unauthorized (triggers onApiError and auth provider’s onUnauthorized)
  • 404 — not found
  • 500 — server error

Static vs. Dynamic Node Serving

For simple use cases, you can serve node metadata as static JSON:
// nodes.json — serve as a static file
const nodes = [
  { id: 'text_input', name: 'Text Input', ... },
  { id: 'http_request', name: 'HTTP Request', ... }
];

app.get('/api/flowdrop/nodes', (req, res) => {
  res.json({ success: true, data: nodes });
});
For dynamic use cases, load from a database:
app.get('/api/flowdrop/nodes', async (req, res) => {
  const nodes = await db
    .collection('nodes')
    .find({
      ...(req.query.category && { category: req.query.category })
    })
    .toArray();
  res.json({ success: true, data: nodes });
});

Verify your backend (conformance checklist)

FlowDrop exercises your API in a predictable order on mount. Run these requests against your base URL to confirm the contract before wiring up the editor — they mirror exactly what the editor does. Replace the BASE value with your own.
BASE=http://localhost:3001/api/flowdrop

# 1. Health — FlowDrop checks this first on mount
curl -s $BASE/health

# 2. Nodes — populates the sidebar
curl -s $BASE/nodes

# 3. Categories — sidebar groups
curl -s $BASE/categories

# 4. Port config — connection compatibility rules
curl -s $BASE/port-config

# 5. Create a workflow (note the returned id)
curl -s -X POST $BASE/workflows \
  -H 'Content-Type: application/json' \
  -d '{"name":"Smoke test","nodes":[],"edges":[]}'

# 6. Load it back (use the id from step 5)
curl -s $BASE/workflows/<id>

# 7. Update it
curl -s -X PUT $BASE/workflows/<id> \
  -H 'Content-Type: application/json' \
  -d '{"name":"Smoke test (edited)","nodes":[],"edges":[]}'
Your backend conforms when:
  • GET /health returns 200 with { "status": "ok" }
  • Every response uses the { "success": true, "data": ... } envelope (the one exception is GET /executions/:id)
  • GET /nodes data items include at least id, name, type, and category
  • Workflow metadata uses the exact field names schemaVersion, createdAt, updatedAt (not created_at / updated_at)
  • POST /workflows returns 201 and echoes a server-assigned id
  • Errors return the correct HTTP status (404 missing, 401 unauthorized) with { "success": false, "error": ... }
  • CORS allows your frontend origin if the backend runs on a different domain
If all seven calls succeed and the checklist passes, mounting the editor against this base URL will load nodes into the sidebar and let you create, edit, and save workflows.

Next Steps