openapi: 3.0.3 info: title: DeerFlow Workflow Studio API (Draft v1) version: "1.0.0-draft" description: | Wire models: deerflow.workflows.schemas / events / errors. Paths marked `x-implemented: false` are still sketches; everything else is mounted today (app/gateway/routers/workflow*.py). Where the phase-0 sketch and the implementation disagreed, this file follows the implementation. servers: - url: http://localhost:8001 description: Local Gateway tags: - name: workflows - name: runs - name: resources - name: data-sources - name: embed - name: coze-compat paths: /api/workflows: get: tags: [workflows] summary: List workflow definitions responses: "200": description: OK post: tags: [workflows] summary: Create workflow definition responses: "201": description: Created /api/workflows/{workflow_id}: get: tags: [workflows] summary: Get workflow definition parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: OK patch: tags: [workflows] summary: Update workflow metadata parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: OK delete: tags: [workflows] summary: Soft-delete / archive parameters: - $ref: "#/components/parameters/WorkflowId" responses: "204": description: Archived /api/workflows/{workflow_id}/copy: post: tags: [workflows] summary: Duplicate a workflow as a new draft owned by the caller parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: Created copy /api/workflows/{workflow_id}/draft: put: tags: [workflows] summary: Save draft (optimistic lock) parameters: - $ref: "#/components/parameters/WorkflowId" requestBody: required: true content: application/json: schema: type: object required: [expectedRevision, graph] properties: expectedRevision: type: integer graph: $ref: "#/components/schemas/WorkflowGraph" responses: "200": description: Saved "409": description: Draft revision conflict /api/workflows/{workflow_id}/validate: post: tags: [workflows] summary: Validate draft without publishing parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: Validation result /api/workflows/{workflow_id}/publish: post: tags: [workflows] summary: Publish immutable version parameters: - $ref: "#/components/parameters/WorkflowId" responses: "201": description: Version created /api/workflows/{workflow_id}/versions: get: tags: [workflows] summary: List published versions parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: OK /api/workflows/{workflow_id}/versions/{version_id}: get: tags: [workflows] summary: Get one published version snapshot parameters: - $ref: "#/components/parameters/WorkflowId" - $ref: "#/components/parameters/VersionId" responses: "200": description: OK /api/workflows/node-types: get: tags: [resources] summary: List supported node types responses: "200": description: OK /api/workflows/resources/agents: get: tags: [resources] summary: Agents available to workflow nodes responses: "200": description: OK /api/workflows/resources/skills: get: tags: [resources] summary: Skills available to workflow nodes responses: "200": description: OK /api/workflows/resources/data-sources: get: tags: [resources] summary: Registered read-only data sources responses: "200": description: OK /api/workflows/resources/subworkflows: get: tags: [resources] summary: Published workflows usable as subworkflows responses: "200": description: OK /api/workflows/data-sources: get: tags: [data-sources] summary: List data sources visible to the caller (own + shared) description: | Never returns the DSN or credential headers — only `masked_target`. responses: "200": description: OK post: tags: [data-sources] summary: Register a data source (`shared: true` is admin-only) description: | `kind: sql` requires `dsn`; `kind: http` requires `headers`. The secret is encrypted on write and only ever decrypted inside the run executor. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DataSourceCreate" responses: "200": description: Created (masked view) "503": description: WORKFLOW_SECRET_KEY not configured /api/workflows/data-sources/{id}: get: tags: [data-sources] summary: Get one data source (masked view) parameters: - $ref: "#/components/parameters/DataSourceId" responses: "200": description: OK put: tags: [data-sources] summary: Update data source (owner or admin) parameters: - $ref: "#/components/parameters/DataSourceId" responses: "200": description: OK delete: tags: [data-sources] summary: Delete data source (owner or admin) parameters: - $ref: "#/components/parameters/DataSourceId" responses: "200": description: '{ "deleted": true }' /api/workflows/data-sources/{id}/introspect: post: tags: [data-sources] summary: Introspect whitelisted schema/table/column metadata description: Returns the configured table allowlist only. Never returns sample rows or secrets. parameters: - $ref: "#/components/parameters/DataSourceId" responses: "200": description: '{ dataSourceId, kind, tables, schemas }' /api/workflows/data-sources/{id}/validate-query: post: tags: [data-sources] summary: Validate SQL against read-only policy (no execution) parameters: - $ref: "#/components/parameters/DataSourceId" requestBody: required: true content: application/json: schema: type: object required: [statement] properties: statement: type: string responses: "200": description: '{ ok: true, statement }' "400": description: WORKFLOW_SQL_POLICY_DENIED /api/workflows/{workflow_id}/runs: post: tags: [runs] summary: Start a run against a published version parameters: - $ref: "#/components/parameters/WorkflowId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/StartRunRequest" responses: "200": description: | Run view plus `created`; `created: false` means the idempotencyKey matched an existing run (scoped per workflow). "400": description: WORKFLOW_INPUT_INVALID / WORKFLOW_VERSION_NOT_FOUND "429": description: WORKFLOW_LIMIT_EXCEEDED (per-user concurrent runs) get: tags: [runs] summary: List runs for a workflow parameters: - $ref: "#/components/parameters/WorkflowId" responses: "200": description: OK /api/workflows/runs/{run_id}: get: tags: [runs] summary: Get run status projection plus current node runs description: | Never includes `resumeToken` — that is delivered on the event stream only. parameters: - $ref: "#/components/parameters/RunId" responses: "200": description: OK "403": description: WORKFLOW_FORBIDDEN (not owner, not admin) "404": description: WORKFLOW_RUN_NOT_FOUND /api/workflows/runs/{run_id}/events: get: tags: [runs] summary: Page durable events parameters: - $ref: "#/components/parameters/RunId" - name: after in: query description: Exclusive `seq` cursor. `after_seq` is accepted as an alias. schema: type: integer default: 0 - name: after_seq in: query schema: type: integer - name: limit in: query schema: type: integer default: 200 maximum: 1000 responses: "200": description: '{ "events": [...], "lastSeq": 42 }' /api/workflows/runs/{run_id}/stream: get: tags: [runs] summary: SSE replay + live tail description: | Replays from `Last-Event-ID` (preferred) or `after` / `after_seq`, then tails live. Closes on the first terminal event; if the run is already terminal without one, a synthesised terminal frame is emitted so the client never hangs. parameters: - $ref: "#/components/parameters/RunId" - name: after in: query schema: type: integer default: 0 - name: after_seq in: query schema: type: integer - name: Last-Event-ID in: header schema: type: string responses: "200": description: text/event-stream headers: X-Workflow-Run-Status: description: Run status at connection time. schema: type: string content: text/event-stream: schema: type: string /api/workflows/runs/{run_id}/cancel: post: tags: [runs] summary: Request cancellation (idempotent) parameters: - $ref: "#/components/parameters/RunId" responses: "200": description: | Run view plus `cancelled`; `false` means it was already terminal. /api/workflows/runs/{run_id}/resume: post: tags: [runs] summary: Resume after human_input parameters: - $ref: "#/components/parameters/RunId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ResumeRunRequest" responses: "200": description: Requeued "409": description: WORKFLOW_RUN_NOT_RESUMABLE / WORKFLOW_RESUME_TOKEN_INVALID /api/workflows/runs/{run_id}/retry: post: tags: [runs] summary: Retry a failed run from the first unfinished node description: | Creates a new queued run with `retryOfRunId` pointing at the failed original. Completed `workflow_node_runs` are copied so the engine resumes from the failed node; if nothing is replayable it is a full re-run of the same version and inputs. Only `failed` runs are retryable. parameters: - $ref: "#/components/parameters/RunId" responses: "201": description: '{ ..., retryOfRunId, created: true }' "409": description: WORKFLOW_RUN_NOT_RETRYABLE "429": description: WORKFLOW_LIMIT_EXCEEDED /api/workflows/runs/{run_id}/artifacts: get: tags: [runs] summary: List artifacts for a run parameters: - $ref: "#/components/parameters/RunId" responses: "200": description: OK /api/workflows/embed/tickets: post: tags: [embed] summary: Issue a short-lived iframe ticket (authenticated parent) description: | `origin` must exactly match `workflows.embed.allowed_origins`. The response also carries `frameAncestors` for the host's CSP. requestBody: required: true content: application/json: schema: type: object required: [workflowId, origin] properties: workflowId: type: string origin: type: string responses: "200": description: '{ ticket, expiresIn, origin, frameAncestors }' "403": description: WORKFLOW_EMBED_ORIGIN_DENIED / WORKFLOW_FORBIDDEN "503": description: No signing key configured /api/workflows/embed/verify: post: tags: [embed] summary: Verify a ticket (single-use, best effort per process) description: | A ticket proves "this origin may embed this workflow for this user right now". It is NOT an API credential — embedded pages still authenticate their API calls with the normal session. requestBody: required: true content: application/json: schema: type: object required: [ticket] properties: ticket: type: string origin: type: string responses: "200": description: '{ ok, workflowId, userId, origin, expiresAt }' "401": description: WORKFLOW_EMBED_TICKET_INVALID (expired or replayed) "403": description: WORKFLOW_EMBED_ORIGIN_DENIED (origin mismatch) /api/workflow_api/canvas: post: tags: [coze-compat] summary: Coze canvas load adapter responses: "200": description: Coze DTO /api/workflow_api/save: post: tags: [coze-compat] summary: Coze canvas save adapter responses: "200": description: Saved /api/workflow_api/create: post: tags: [coze-compat] summary: Coze create workflow adapter responses: "200": description: Created /api/workflow_api/node_template_list: post: tags: [coze-compat] summary: Coze node template list adapter responses: "200": description: Templates components: parameters: WorkflowId: name: workflow_id in: path required: true schema: type: string VersionId: name: version_id in: path required: true schema: type: string RunId: name: run_id in: path required: true schema: type: string DataSourceId: name: id in: path required: true schema: type: string schemas: WorkflowGraph: type: object description: Internal standard graph (see WORKFLOW_SCHEMA_V1_ZH.md) required: [schemaVersion, id, nodes, edges] properties: schemaVersion: type: string enum: ["1.0"] id: type: string name: type: string description: type: string inputSchema: type: object outputSchema: type: object nodes: type: array items: $ref: "#/components/schemas/WorkflowNode" edges: type: array items: $ref: "#/components/schemas/WorkflowEdge" settings: $ref: "#/components/schemas/WorkflowGraphSettings" WorkflowNode: type: object required: [id, type] properties: id: type: string type: type: string enum: - start - output - agent - skill - http - sql_read - code - transform - condition - merge - human_input - subworkflow - loop name: type: string config: type: object WorkflowEdge: type: object required: [id, source, target] properties: id: type: string source: type: string target: type: string sourcePort: type: string nullable: true WorkflowGraphSettings: type: object properties: runTimeoutSeconds: type: integer default: 1800 nodeTimeoutSeconds: type: integer default: 300 maxSteps: type: integer default: 200 maxLoopIterations: type: integer default: 5 maxParallelism: type: integer default: 4 NodeResult: type: object properties: data: type: object messages: type: array items: type: object artifacts: type: array items: $ref: "#/components/schemas/ArtifactRef" metadata: type: object warnings: type: array items: type: string ArtifactRef: type: object required: [artifactId, name] properties: artifactId: type: string name: type: string mimeType: type: string path: type: string sizeBytes: type: integer nullable: true preview: type: string nullable: true StartRunRequest: type: object properties: versionId: type: string description: Omit to use the latest published version. inputs: type: object idempotencyKey: type: string maxLength: 128 executionMode: type: string enum: [normal] default: normal ResumeRunRequest: type: object required: [resumeToken] properties: resumeToken: type: string nodeId: type: string description: Defaults to the paused node from `pendingInput`. action: type: string default: submit values: type: object DataSourceCreate: type: object required: [name] properties: name: type: string description: type: string kind: type: string enum: [sql, http] default: sql dsn: type: string description: Write-only; required for `kind: sql`. headers: type: object additionalProperties: type: string description: Write-only; required for `kind: http`. maxRows: type: integer default: 1000 allowedTables: type: array items: type: string enabled: type: boolean default: true shared: type: boolean default: false description: Admin-only. Shared sources have a null owner. WorkflowEventEnvelope: type: object required: [schemaVersion, runId, workflowId, versionId, seq, event] properties: schemaVersion: type: string enum: ["1.0"] runId: type: string workflowId: type: string versionId: type: string seq: type: integer event: type: string nodeId: type: string nullable: true nodeRunId: type: string nullable: true timestamp: type: string format: date-time data: type: object WorkflowErrorBody: type: object required: [code, message] properties: code: type: string message: type: string retryable: type: boolean nodeId: type: string nullable: true details: type: object