> ## Documentation Index
> Fetch the complete documentation index at: https://flowdrop.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend implementation

> Build the REST API that FlowDrop expects to communicate with.

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:

| Method | Path             | Purpose                                      |
| ------ | ---------------- | -------------------------------------------- |
| `GET`  | `/health`        | Health check (FlowDrop checks this on mount) |
| `GET`  | `/nodes`         | List available node types                    |
| `GET`  | `/workflows/:id` | Load a workflow                              |
| `POST` | `/workflows`     | Create a new workflow                        |
| `PUT`  | `/workflows/:id` | Update an existing workflow                  |

### Tier 2: Full Editor Experience

These endpoints enable the complete sidebar, categories, and port validation:

| Method   | Path             | Purpose                                    |
| -------- | ---------------- | ------------------------------------------ |
| `GET`    | `/categories`    | Node category definitions (sidebar groups) |
| `GET`    | `/port-config`   | Port data types and compatibility rules    |
| `GET`    | `/nodes/:id`     | Get a single node's metadata               |
| `GET`    | `/workflows`     | List all workflows                         |
| `DELETE` | `/workflows/:id` | Delete a workflow                          |

### Tier 3: Advanced Features

These enable playground, execution, and interrupts:

| Method | Path                                 | Purpose                   |
| ------ | ------------------------------------ | ------------------------- |
| `POST` | `/workflows/:id/execute`             | Execute a workflow        |
| `GET`  | `/executions/:id`                    | Get execution status      |
| `POST` | `/workflows/:id/playground/sessions` | Create playground session |
| `GET`  | `/playground/sessions/:sid/messages` | Poll for messages         |
| `POST` | `/playground/sessions/:sid/messages` | Send user message         |
| `GET`  | `/interrupts/:id`                    | Get pending interrupt     |
| `POST` | `/interrupts/:id`                    | Resolve an interrupt      |
| `GET`  | `/system/config`                     | Runtime configuration     |

<Note>
  **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](#advanced-endpoint-formats-tier-3). For the exhaustive schema, see the
  [OpenAPI specification](/api-reference/introduction).
</Note>

## Base URL Configuration

All paths above are relative to a base URL you configure:

```typescript theme={null}
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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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.
* `icon` — [Iconify](https://icon-sets.iconify.design/) 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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "inputs": { "text_input": "Hello" },
  "options": { "timeout": 30000, "maxSteps": 50 }
}
```

**Response (`202 Accepted`):**

```json theme={null}
{
  "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.

<Note>
  **This endpoint returns the status object directly — no `success`/`data`
  envelope.** It is the one exception to the response wrapper.
</Note>

**Response:**

```json theme={null}
{
  "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:**

```json theme={null}
{ "name": "Test Session 1" }
```

**Response (`201`):**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "content": "Process this file",
  "inputs": { "file_path": "/data/input.csv" }
}
```

**Response (`200`):**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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:**

```json theme={null}
{ "value": true }
```

| Interrupt `type` | `value` shape                     |
| ---------------- | --------------------------------- |
| `confirmation`   | `boolean`                         |
| `choice`         | `string` or `string[]`            |
| `text`           | `string`                          |
| `form`           | `object` matching the form schema |
| `review`         | decisions 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:**

```json theme={null}
{
  "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:

```typescript theme={null}
// 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:

```json theme={null}
{
  "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**:

```typescript theme={null}
// 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:

```typescript theme={null}
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.

```bash theme={null}
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

* [Backend: Express.js](/recipes/backend-express) — get a working backend in 15 minutes
* [Framework Integration](/guides/integration) — connect FlowDrop to your backend
* [API Overview](/reference/api-overview) — complete module and endpoint reference
* [OpenAPI Specification](/api-reference/introduction) — full API contract
