Skip to content

feat(api): add workflow export and import endpoints to the public v1 API - #5999

Merged
waleedlatif1 merged 4 commits into
stagingfrom
worktree-workflow-import-api
Jul 28, 2026
Merged

feat(api): add workflow export and import endpoints to the public v1 API#5999
waleedlatif1 merged 4 commits into
stagingfrom
worktree-workflow-import-api

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Add GET /api/v1/workflows/{id}/export — returns a portable JSON envelope (version, exportedAt, workflow, state) matching the admin export's shape
  • Add POST /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 API
  • The admin namespace already had both halves; the public v1 surface had neither
  • Public export is secret-sanitized (sanitizeForExport): stored credentials and password: true fields are redacted, {{ENV_VAR}} references and block positions preserved. Admin export stays raw for backup/restore
  • Import regenerates block/edge/loop/parallel ids and de-duplicates the name against the target folder, so the same payload can be imported repeatedly and alongside its source
  • Export requires read, import requires write on the workspace. Export masks permission failures as 404 to match the rest of the v1 workflow read surface. Import authorizes before parsing any payload
  • Import body capped at 10 MB (well under the platform-wide 50 MB default)
  • Export records a workflow.exported audit entry; import inherits workflow.created from the shared orchestrator
  • Document both in apps/docs/openapi.json + the workflows nav, with new WorkflowExport, WorkflowExportState and ImportedWorkflow schemas
  • Move parseWorkflowVariables from app/api/v1/admin/types.ts to lib/workflows/variables/parse.ts so a public route doesn't import from the admin namespace (4 admin consumers repointed)

Type of Change

  • New feature

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:strict and the monorepo boundary check all pass. Not exercised against a live server.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

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.
@vercel

vercelBot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
docsSkippedSkippedJul 28, 2026 3:27am

Request Review

@cursor

cursorBot commented Jul 28, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Introduces a workspace write path that persists full workflow graphs and handles secret redaction on export; behavior is heavily tested and aligned with existing editor/admin flows, but mistakes could leak credentials or leave broken workflows.

Overview
Adds public v1 workflow portability: GET /api/v1/workflows/{id}/export returns a versioned JSON envelope (with sanitizeForExport redacting stored credentials and workspace-scoped bindings while keeping {{ENV_VAR}} refs), and POST /api/v1/workflows/import creates a new workflow from that envelope, bare state, or a JSON string—with write auth, 10 MB cap, id regeneration, name dedup, transactional graph + variables persistence, and rollback on failure.

Refactors shared import/export plumbing: prepareWorkflowStateForPersistence is extracted so editor PUT /api/workflows/[id]/state, v1 import, and admin import normalize graphs the same way; parseWorkflowVariables / normalizeImportedVariables move to lib/workflows/variables/parse; admin import stops writing raw parsed state. OpenAPI, docs nav, v1 contracts/middleware, and route tests (plus round-trip / prepare-state coverage) are updated; import id regeneration also escapes regex metacharacters in block-name reference rewrites to avoid ReDoS on hostile payloads.

Reviewed by Cursor Bugbot for commit a6d6213. Configure here.

@greptile-apps

greptile-appsBot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds public v1 workflow export/import APIs with sanitization, atomic import persistence, docs, and shared variable parsing.

  • GET /api/v1/workflows/{id}/export returns a portable envelope via sanitizeForExport (block secrets/workspace bindings redacted; variables and {{ENV_VAR}} kept by design)
  • POST /api/v1/workflows/import accepts export envelope, bare state, or JSON string; regenerates ids; write-gated; graph + variables in one transaction with shell-row rollback
  • Docs/OpenAPI, contracts, admin import aligned on prepareWorkflowStateForPersistence / normalizeImportedVariables; parseWorkflowVariables moved out of admin types

Confidence Score: 5/5

Safe 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.

Important Files Changed

FilenameOverview
apps/sim/app/api/v1/workflows/import/route.tsImport 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.tsPublic export sanitizes block secrets/bindings and documents that variables remain plaintext configuration matching GET workflow.
apps/sim/lib/workflows/variables/parse.tsShared parse/normalize helpers for export and import variable shapes (including legacy arrays).
apps/sim/lib/workflows/persistence/prepare-state.tsShared 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
Loading

Reviews (4): Last reviewed commit: "fix(api): cap import names inside the bo..." | Re-trigger Greptile

Comment threadapps/sim/app/api/v1/workflows/[id]/export/route.ts
Comment threadapps/sim/app/api/v1/workflows/import/route.ts
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

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/app/api/v1/workflows/import/route.ts
…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

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@waleedlatif1
waleedlatif1 merged commit ca13cc9 into stagingJul 28, 2026
20 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-workflow-import-api branch July 28, 2026 05:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@waleedlatif1