Uh oh!
There was an error while loading. Please reload this page.
feat(billing): gate programmatic workflow execution behind a paid plan - #5036
Conversation
Block free-plan accounts (hosted only) from running workflows programmatically: API-key/public execute, MCP server, A2A agent server, generic webhooks, and cross-origin chat embeds. Returns 402 (403 for chat embeds) with an upgrade message; provider webhooks, session/browser runs, internal-JWT executor traffic, and self-hosted are unaffected. - Add isApiExecutionEntitled / isWorkspaceApiExecutionEntitled gate helpers - Gate the execute, mcp/serve, a2a/serve, webhooks/trigger, and chat routes - Deploy modal: show an upgrade prompt on the API/MCP/A2A tabs for free users - Upgrade page: rename Pro feature to 'Deploy workflows as APIs'; API endpoint rate-limit row shows 0 for Free
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview API enforcement: Workflow Product UI: Deploy modal API/MCP/A2A tabs show Tests cover entitlement helpers, embed rules, and route 402/403 behavior. Reviewed by Cursor Bugbot for commit 9687476. Bugbot is set up for automated code reviews on this repo. Configure here. |
TheodoreSpeaks
commented
Jun 14, 2026
@greptile review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Greptile SummaryThis PR gates programmatic workflow execution (API key, public API, MCP server, A2A agent server, generic webhooks, cross-origin chat embeds) behind a paid plan on hosted deployments, returning 402 for free-plan callers. Provider webhooks (Slack, GitHub, etc.), session-based runs, and self-hosted deployments are explicitly left unaffected.
Confidence Score: 4/5Safe to merge for security correctness; one UI/API mismatch in the deploy modal should be addressed before the dark-rollout flag is used in production. All API routes correctly gate on the workspace billed account and return 402. The one concrete defect is in the deploy modal: its gate condition reads apps/sim/app/workspace/.../deploy-modal/deploy-modal.tsx — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming request] --> B{Auth type?}
B -->|Session / Internal JWT| Z[Proceed - not gated]
B -->|API key| C[isWorkspaceApiExecutionEntitled]
B -->|Public API| C
B -->|MCP serve| C
B -->|A2A serve| D[isApiExecutionEntitled via billedUserId]
B -->|Generic webhook| C
B -->|Cross-origin chat embed| C
C --> E{isBillingEnabled AND isFreeApiDeploymentGateEnabled?}
D --> E
E -->|No - self-hosted or gate off| Z
E -->|Yes| F[getWorkspaceBilledAccountUserId]
F -->|null| Z2[Fail-open - allow]
F -->|userId| G[getHighestPrioritySubscription]
G -->|paid plan| Z
G -->|free / null| H[Return 402 / 403]
style H fill:#f66,color:#fff
style Z fill:#6a6,color:#fff
style Z2 fill:#fa6,color:#fff
Reviews (6): Last reviewed commit: "feat(billing): add FREE_API_DEPLOYMENT_G..." | 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.
These pre-existing tests exercise gated routes with API-key/generic-webhook paths; on hosted (CI) the gate now returns 402. Mock the gate as entitled by default and add a generic-webhook 402 coverage case.
TheodoreSpeaks
commented
Jun 14, 2026
@greptile review |
Greptile SummaryThis PR gates programmatic workflow execution (API key, public execute, MCP, A2A, generic webhooks, cross-origin chat embeds) behind a paid plan on hosted deployments, returning 402/403 for free-tier accounts. New
Confidence Score: 4/5The core billing helpers and most route integrations are correct and well-tested; the two points of concern are an error-response regression in the multi-webhook fan-out path and a visual flash in the deploy modal that does not affect backend enforcement. The webhook fan-out loop silently swallows 402 responses when multiple generic webhooks are all blocked, returning a misleading 500 to the caller. This is a real, reproducible failure: any credential-set fan-out with every entry being a blocked generic webhook triggers it. The execute route also uses the workflow creator's user ID for the public-API billing check rather than the workspace billing account, which could mismatch after org membership changes. apps/sim/app/api/webhooks/trigger/[path]/route.ts — the fan-out billing gate; apps/sim/app/api/workflows/[id]/execute/route.ts — the public-API billing identity. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Incoming programmatic request] --> B{Route type?}
B -->|API key / public execute| C[execute/route.ts]
B -->|MCP server| D[mcp/serve/route.ts]
B -->|A2A agent| E[a2a/serve/route.ts]
B -->|Webhook| F[webhooks/trigger/route.ts]
B -->|Chat embed| G[chat/utils.ts assertChatEmbedAllowed]
C --> H{isApiExecutionEntitled userId}
D --> I{isWorkspaceApiExecutionEntitled workspaceId}
E --> J{isApiExecutionEntitled billedUserId}
F --> K{provider === generic?}
G --> L{isHosted AND cross-origin?}
K -->|yes| M{isWorkspaceApiExecutionEntitled workspaceId}
K -->|no provider webhook| N[Allow]
H -->|not entitled| O[402]
I -->|not entitled| O
J -->|not entitled| O
M -->|not entitled single webhook| O
M -->|not entitled multi webhook| P[continue - may produce 500]
L -->|yes| Q{isWorkspaceApiExecutionEntitled}
H -->|entitled| R[Execute]
I -->|entitled| R
J -->|entitled| R
M -->|entitled| R
Q -->|not entitled| S[403]
Q -->|entitled| R
L -->|same origin| R
Reviews (2): Last reviewed commit: "test(billing): mock api-execution gate i..." | 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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- execute route: gate on the workflow's workspace billed account (like MCP/A2A/ webhooks/chat) instead of the caller/creator's personal plan, so a paid workspace is never 402'd because an individual is on free - webhooks: all-generic-all-free fan-out now returns 402, not a 500 'No webhooks processed' fallback - deploy modal: hold the gate closed until the subscription query resolves (isFree(undefined) is true) to avoid flashing the upgrade wall at paid users
TheodoreSpeaks
commented
Jun 14, 2026
@greptile review |
TheodoreSpeaks
commented
Jun 14, 2026
Addressed the Bugbot/Greptile findings in 57a0f47:
Intentionally left as-is:
|
TheodoreSpeaks
commented
Jun 14, 2026
Re-review on 57a0f47 re-raised a few items — status: Already fixed in 57a0f47 (stale re-raises):
Fail-open findings (api-access.ts L39, chat utils L87) — not a real leak, leaving as-is: |
The paywall should follow billing enforcement, not the hostname. Keying off isHosted would still 402 free users on a hosted deployment with BILLING_ENABLED unset. Switch the server gate (api-access, chat embed) to isBillingEnabled and the deploy-modal UI to the client NEXT_PUBLIC_BILLING_ENABLED flag (matching the Inbox paywall), so a billing-disabled deployment skips the gate entirely.
TheodoreSpeaks
commented
Jun 14, 2026
@greptile review |
Gate the programmatic-execution paywall behind a dedicated backend env flag (combined with BILLING_ENABLED), off by default, so it can ship dark and be enabled per-deployment after a backend sanity check. Backend only — the deploy modal UI is unchanged.
TheodoreSpeaks
commented
Jun 14, 2026
@greptile review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9687476. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
TheodoreSpeaks
commented
Jun 14, 2026
Both Bugbot items on the latest commit are re-raises of already-addressed findings (Bugbot re-reviews the full diff each push):
No new changes needed. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…#5678) * feat(billing): allow programmatic workflow execution on the free plan Remove the free-plan paywall on programmatic execution (API-key/public execute, MCP serve, generic webhooks, cross-origin chat embeds) added in #5036. The gate shipped dark behind FREE_API_DEPLOYMENT_GATE_ENABLED to curb bot abuse; with that abuse handled upstream, free workspaces can use the API again (still subject to the free-tier rate limits). - Delete isWorkspaceApiExecutionEntitled / api-access.ts and the FREE_API_DEPLOYMENT_GATE_ENABLED env flag - Ungate the execute, mcp/serve, and webhooks/trigger routes and the chat embed check (assertChatEmbedAllowed removed) - Remove the deploy-modal upgrade wall (DeployUpgradeGate) and the now unused useWorkspaceOwnerBilling client hook; the owner-billing endpoint stays as reusable billing infrastructure - Restore pre-gate upgrade-page copy (Pro feature line, free API endpoint rate-limit column) * chore(billing): drop orphaned owner-billing endpoint and dead chat logger Self-review follow-ups: the /api/workspaces/[id]/owner-billing route + contract existed solely for the deploy-modal gate's client hook (deleted here); host-context serves the same ownerBilling data server-side, so the standalone endpoint is dead HTTP surface. workspaceOwnerBillingSchema and the WorkspaceOwnerBilling type stay (embedded in workspaceHostContextSchema). Also removes the now-unused ChatAuthUtils logger. * style: biome formatting after gate removal

Summary
execute, MCP server, A2A agent server, generic ("custom") webhooks, and cross-origin chat embeds. Returns 402 (403 for chat embeds) with an upgrade message.isApiExecutionEntitled/isWorkspaceApiExecutionEntitledhelpers (hosted-only, roll up org subs) gate each route inline.0for Free.Type of Change
Testing
Tested manually (curl: free API key → 402 on execute/MCP/A2A; generic webhook with valid token → 402; cross-origin chat → 403; first-party/session unaffected). Added/updated unit + route tests.
bun run lintandbun run check:api-validation:strictpass.Checklist