feat(api): add workflow export and import endpoints to the public v1 API - #5999
Conversation
Adds GET /api/v1/workflows/[id]/export and POST /api/v1/workflows/import.
The export envelope is accepted verbatim by import, so workflows round-trip
between workspaces over the public API.
Unlike the admin export, the public export is secret-sanitized: stored
credentials and password fields are redacted while {{ENV_VAR}} references
and block positions are preserved. Import regenerates block, edge, loop and
parallel ids and de-duplicates the workflow name against the target folder.
Also moves parseWorkflowVariables out of the admin types module into
lib/workflows/variables/parse.ts so the public route does not import from
the admin namespace.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Refactors shared import/export plumbing: Reviewed by Cursor Bugbot for commit a6d6213. Configure here. |
Greptile SummaryAdds public v1 workflow export/import APIs with sanitization, atomic import persistence, docs, and shared variable parsing.
Confidence Score: 5/5Safe to merge; prior export-docs and import atomicity threads are addressed and no remaining blocking failures were identified. Import now writes graph and variables in a single transaction and deletes the shell row on any failure, with regression coverage; export documents intentional variable pass-through aligned with existing read APIs. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/api/v1/workflows/import/route.ts | Import authorizes write, validates state, then persists graph and variables in one transaction with compensating delete on failure (addresses prior half-land issue). |
| apps/sim/app/api/v1/workflows/[id]/export/route.ts | Public export sanitizes block secrets/bindings and documents that variables remain plaintext configuration matching GET workflow. |
| apps/sim/lib/workflows/variables/parse.ts | Shared parse/normalize helpers for export and import variable shapes (including legacy arrays). |
| apps/sim/lib/workflows/persistence/prepare-state.ts | Shared persistence preparation used by public and admin import paths. |
Sequence Diagram
sequenceDiagram
participant Client
participant Export as GET /v1/workflows/{id}/export
participant Import as POST /v1/workflows/import
participant DB as Postgres
Client->>Export: API key + workflow id
Export->>DB: load workflow + normalized state
Export-->>Client: envelope (sanitized blocks, variables as stored)
Client->>Import: workspaceId + envelope
Import->>DB: create shell workflow
Import->>DB: transaction save graph + variables
alt persist fails
Import->>DB: delete shell workflow
Import-->>Client: 500
else success
Import-->>Client: 201 ImportedWorkflow
end
Reviews (4): Last reviewed commit: "fix(api): cap import names inside the bo..." | Re-trigger Greptile
Writes the imported graph and its variables in a single transaction and deletes the shell workflow row on any failure, so a caller that receives an error is never left with a partially imported workflow. Previously a throw from the variables update returned 500 while leaving the workflow behind with an empty variables map. Also narrows the export route's sanitization claim: workflow variables are emitted as stored, matching GET /api/v1/workflows/[id] and the in-app export. They are plaintext configuration readable at the same permission level this route requires; secrets belong in environment variables, which travel as unresolved references.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5decc01. Configure here.
…eline Security: - Escape block names before interpolating them into a RegExp in updateValueReferences. Names reach it straight from imported workflow JSON and normalizeWorkflowBlockName preserves regex metacharacters, so a name like `a*a*a*a*b` compiled to a catastrophically backtracking pattern. A sub-kilobyte body blocked the event loop for 50s and grew exponentially. Also skip rename-to-itself, which is the entire map on the import path, so the scan no longer runs at all there. - Validate folder ownership before folder lock state, so a locked folder in another workspace can no longer be distinguished from a missing one. Correctness: - Gate the imported graph on workflowStateSchema, the same schema the canonical PUT /api/workflows/[id]/state path enforces. Without it a valid 201 could persist a block field of the wrong type, which then threw on every subsequent read and left a workflow nothing could open. - Guard the compensating delete so a failed rollback logs the orphaned id instead of vanishing into a generic 500. - Validate variable `type` against the enum and build the record on a null-prototype object, so a `__proto__` key no longer silently drops the variable. - Bound payload-derived names and descriptions to the same limits the contract declares for the explicit overrides. - Return the description as stored rather than coercing '' to null, matching GET /api/v1/workflows/[id]. Shared code, so the two write paths cannot drift: - Extract prepareWorkflowStateForPersistence and use it from both PUT /api/workflows/[id]/state and the v1 import route: agent-tool sanitization, block backfill, dangling-edge removal, and loop/parallel recomputation now have one implementation. - Persist inline custom tools on import, which the canonical path already did. - Move variable normalization into lib/workflows/variables and repoint the admin importer at it, removing the last duplicate. Docs: - OpenAPI: oneOf -> anyOf on the import body. WorkflowExport matches any object, so every valid object payload matched two branches and failed validation under any spec-driven validator. Document 423 and the loss of workspace-scoped bindings on export. Tests: prepare-state unit tests and a real export -> import round trip with no mocks of the sanitizer or parser, covering loop/parallel children and the regex-metacharacter payload.
|
@cursor review |
…t paths - `truncate` appends its suffix after slicing, so capping at the contract limit produced 203/2003-character values — past the very bound the cap exists to enforce, and into the headroom reserved for dedup suffixes. Reserve the ellipsis inside the limit. - Match `extractWorkflowName`'s candidate order (state.metadata.name before workflow.name) and trim, so the v1 API and the in-app importer resolve the same name for the same payload. Previously a hand-authored payload carrying both could yield two different names. - Run the admin importer through prepareWorkflowStateForPersistence too. It was writing raw parsed state, so a dangling edge tripped the workflow_edges foreign key and a block missing its backfilled columns could land unopenable — the same class this PR just closed on the v1 path.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a6d6213. Configure here.
Summary
GET /api/v1/workflows/{id}/export— returns a portable JSON envelope (version,exportedAt,workflow,state) matching the admin export's shapePOST /api/v1/workflows/import— accepts that envelope verbatim, a bare workflow state, or a JSON string of either, so workflows round-trip across workspaces over the public APIsanitizeForExport): stored credentials andpassword: truefields are redacted,{{ENV_VAR}}references and block positions preserved. Admin export stays raw for backup/restoreread, import requireswriteon the workspace. Export masks permission failures as 404 to match the rest of the v1 workflow read surface. Import authorizes before parsing any payloadworkflow.exportedaudit entry; import inheritsworkflow.createdfrom the shared orchestratorapps/docs/openapi.json+ the workflows nav, with newWorkflowExport,WorkflowExportStateandImportedWorkflowschemasparseWorkflowVariablesfromapp/api/v1/admin/types.tstolib/workflows/variables/parse.tsso a public route doesn't import from the admin namespace (4 admin consumers repointed)Type of Change
Testing
26 new tests (8 export, 18 import); 50 pass across
app/api/v1/workflows/. Covers auth, permission masking, secret redaction, env-var preservation, edge-handle normalization, all three accepted payload shapes, metadata precedence, legacy array-form variables, and rollback of the created row when persisting state fails.bunx tsc --noEmit,bun run lint,bun run check:api-validation:strictand the monorepo boundary check all pass. Not exercised against a live server.Checklist