Uh oh!
There was an error while loading. Please reload this page.
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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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.
waleedlatif1
commented
Jul 28, 2026
waleedlatif1
commented
Jul 28, 2026
@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.
waleedlatif1
commented
Jul 28, 2026
waleedlatif1
commented
Jul 28, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
…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.
waleedlatif1
commented
Jul 28, 2026
waleedlatif1
commented
Jul 28, 2026
@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