Paid Public Flows
Quick Answer
To sell a flow today:
- Open the creator workspace billing page and complete seller payout onboarding.
- Open the flow's Publish page.
- Save the public listing, pricing or price catalog, and optional trial settings.
- Publish the current draft as the live buyer version.
- Verify buyer checkout from another workspace.
Example:
- Seller onboarding:
/<workspace-id>/billing - Flow publishing:
/<workspace-id>/flows/<slug>/publish - Public app page:
https://breyta.ai/apps/<flow-slug> - Buyer verification: sign in as a different workspace and open
/<buyer-workspace-id>/discover
Breyta handles buyer checkout, Stripe webhooks, entitlement state, and payout routing.
The creator does not create Stripe Checkout Sessions, products, or prices manually.
The creator does need a seller account for their workspace so payouts can be routed correctly.
Paid publishing and seller payout setup are currently behind the
BREYTA_FLOW_REUSE_APPROVER_USER_IDS rollout gate. If the Publish page or
seller-payout controls are missing, confirm that the current user id is included
in that allow-list.
What Breyta Handles Vs What The Creator Handles
| Area | Breyta handles | Creator handles |
|---|---|---|
| Buyer payments | Stripe Checkout Sessions, webhook delivery, checkout completion, entitlement state | Nothing directly in Stripe Checkout |
| Seller payouts | Connected-account routing, payout transfer metadata, Connect account persistence on the workspace | Completing Stripe onboarding and giving Stripe required payout details |
| Flow publishing | Runtime install/check/run/release machinery | Authoring the flow, setting metadata, releasing the live version |
Seller Onboarding (Once Per Workspace)
Breyta uses Stripe Connect seller accounts for creator payouts.
The current integration creates an Express connected account for the creator workspace and routes buyer payments to that account.
Creator onboarding path:
- Open
/<workspace-id>/billing. - In the seller payouts card, start or resume Stripe onboarding.
- Complete the Stripe-hosted onboarding flow.
- Return to Breyta. The billing page refreshes seller payout state through
?billing_refresh=seller.
The workspace is ready to sell when the Connect state is effectively active.
In sandbox validation, the ready state corresponded to:
charges_enabled=truepayouts_enabled=truecard_payments=activetransfers=active
If seller onboarding is incomplete, buyer checkout is blocked with:
Creator payouts are not configured for this flow
Operators can still use the admin Stripe Connect endpoints for diagnostics and
sandbox runbooks, but they are no longer the normal creator path.
Publish From The Flow
Use the dedicated Publish page for the flow:
/<workspace-id>/flows/<slug>/publish
The Publish page owns the creator-facing paid-flow controls:
- public listing visibility for Discover/install surfaces
- free, one-time, subscription, prepaid usage pack, or subscription usage pricing
- optional price catalogs for multiple plans
- subscription interval
- before-install paid checkout
- prepaid run pack quantity for usage pricing
- included runs for subscription usage pricing
- optional subscription trial days
- whether a trial requires a payment method
- draft-to-live publishing
- seller payout readiness
- installs and checkout activity
For a paid flow:
- Make sure the flow has a saved draft.
- Open the Publish page.
- Choose
One-time purchase,Subscription,Prepaid run pack, orSubscription usage. - Enter amount and currency.
- For subscriptions, choose the billing interval and optional trial settings.
- For prepaid usage packs, enter the run quantity. For subscription usage, enter included runs per billing period.
- Enable public listing.
- Save settings.
- Publish changes.
Public paid flows can include manual interfaces, schedules, and webhook
interfaces. End users manage automatic entrypoint enablement per installation.
The checkout path charges one-time purchases, subscriptions, prepaid usage
packs, and subscription-usage plans through Stripe Checkout. Usage packs and
subscription usage then rely on Breyta's run ledger for allowance tracking.
Trial-run packages can bypass Stripe first and start with a Breyta trial
entitlement when the selected pricing shape supports that path.
Advanced source/CLI workflows can still represent paywall.when explicitly.
Breyta's default public-flow platform fee is 5% plus 30 cents USD. One-time
USD checkout applies both components. Subscription checkout applies the
percentage component through Stripe Checkout; fixed subscription fee settlement
is tracked separately.
The publish action creates the live version buyers install. If the draft differs
from the live version, the page shows that the draft needs publishing.
Author Monetization In Source (Advanced)
The Publish page is the normal creator path. Source-first authoring remains
useful for code-reviewed flow definitions, fixtures, and CLI-heavy operations.
The flow file can carry marketplace monetization metadata directly, and
breyta flows push persists it. For new paid apps, author the catalog under
:marketplace {:app {... :monetization {:plans [...]}}}. Legacy flow-level
:marketplace {:monetization ...} is still supported for existing listings,
but new paid apps should use app-owned identity so Discover, checkout, billing,
and install ownership all use the same app id and plan ids.
Pricing Models
Choose the pricing type based on what the buyer is purchasing:
| Pricing type | Buyer pays | Buyer receives | Typical use case | Required fields |
|---|---|---|---|---|
:free | Nothing | Free install and run access | Open tools, lead-gen flows, free templates | pricing.type |
:one-time | One upfront payment | Paid access without recurring billing | Fixed-fee utility, template, or app | amount, currency |
:subscription | Recurring monthly or yearly charge | Access while the subscription stays active | SaaS-style app access | amount, currency, interval |
:usage | One-time payment for a prepaid run pack | A fixed number of successful runs with no time expiry | "$99 for 500 runs" | amount, currency, unit, included-quantity |
:subscription-usage | Recurring monthly or yearly charge | A fixed number of successful runs every billing period | "$49/mo includes 22 runs" | amount, currency, interval, unit, included-quantity |
Pricing guidance:
- Prefer subscriptions, subscription usage, and prepaid usage packs for paid
marketplace apps. - Avoid tiny payments and per-run microtransactions. If the app is mainly for
sampling or lead generation, use free access or trial runs instead. - Price from buyer value first, then check costs and margins with run cost
estimates, configured step:metering, external API or LLM costs, payment
fees, Breyta platform fees, and expected support burden. - Breyta apps can have low operational overhead, so the cost floor may be low;
that does not mean every app should be priced at the floor. - Good starting shapes are
$49/mowith included runs,$99prepaid packs, or
meaningful fixed-fee purchases such as$199diagnostics.
Operational rules:
:usageis prepaid packaging, not pay-as-you-go metering.:usagepacks do not expire by time; they are consumed only by successful completed runs.:subscription-usageresets allowance on subscription renewal.:subscription-usagedoes not currently bill paid overages. When included runs are exhausted, the buyer is blocked until renewal or a future product change.- Failed, cancelled, terminated, and timed-out runs do not consume paid allowance.
Trial Options
Supported trial shapes depend on the pricing type:
| Pricing type | Supported trial |
|---|---|
:one-time | trial.runs |
:subscription | trial.days, optional trial.payment-method-required |
:usage | trial.runs |
:subscription-usage | trial.runs, or pricing.free-quantity as the fallback no-card trial-run allowance when explicit trial.runs is absent |
Important trial details:
trial.daysis the subscription-style time-based trial.trial.runsis the run-counted trial.pricing.free-quantityon:subscription-usageis a PLG-style fallback trial entry point, not a second paid allowance bucket.- If both
trial.runsandpricing.free-quantityare present on:subscription-usage, explicittrial.runswins.
Supported values:
pricing.type::free,:one-time,:subscription,:usage,:subscription-usagepricing.amount: positive price in major currency units. Decimals are allowed when the currency supports them, for example"49.50"USD. Marketplace pricing guidance still discourages tiny checkout amounts.pricing.currency: lowercase ISO currency code such as"usd"pricing.interval::monthor:yearfor subscriptions and subscription usagepricing.unit::runfor usage and subscription usagepricing.free-quantity: zero or more free trial runs for:usage, or fallback no-card trial runs for:subscription-usagewhentrial.runsis not setpricing.included-quantity: positive paid run quantity for:usage, or positive included runs per billing period for:subscription-usagepaywall.when::before-installor:before-runfor paid flows. For
app-owned plan catalogs, put:paywall {:when ...}on each plan when timing
is plan-specific; a root:paywallunder:monetizationremains supported
as a shared default.trial.days: positive integer, optionaltrial.payment-method-required:trueorfalse, optionalplans: app-owned catalog entries under
:marketplace {:app {:monetization {:plans [...]}}}. Every plan must
normalize to a unique stableplan-id, every entry must be valid, and at
most one plan can be markeddefaultorrecommended. If no default is set,
the first plan is treated as the default.plans[].visible: set tofalseto keep an existing plan stored but hide it
from new public plan selection.plans[].retired: set totrueto keep an existing plan stored but remove it
from new public plan selection. Use this for obsolete plan IDs that historical
checkout, entitlement, billing, or audit records may still reference.- Seat-based pricing is not implemented. Do not describe a plan as "N seats" or
"N installs" unless the product has added explicit seat entitlements. Use
run quantities only for usage-priced plans.
New app-owned source example:
{:slug :customer-insights
:name "Customer Insights"
:tags ["analytics" "customer-insights"]
:discover {:public true}
:marketplace
{:visible true
:app
{:app-id "customer-insights-suite"
:app-name "Customer Insights"
:app-primary-flow-slug "customer-insights"
:app-flow-slugs ["customer-insights"]
:monetization
{:plans
[{:plan-id "starter"
:name "Starter"
:default true
:pricing {:type "subscription"
:amount 29
:currency "usd"
:interval "month"}
:paywall {:when :before-install}
:trial {:days 7
:payment-method-required false}}
{:plan-id "pro-pack"
:name "Pro Pack"
:pricing {:type "usage"
:amount 49
:currency "usd"
:unit "run"
:included-quantity 250}
:paywall {:when :before-install}}
{:plan-id "metered"
:name "Metered"
:pricing {:type "subscription-usage"
:amount 99
:currency "usd"
:interval "month"
:unit "run"
:included-quantity 1000}
:paywall {:when :before-install}}]}}}}
Use a single plan in the catalog for a one-price app. Existing legacy
flow-level examples with :marketplace {:monetization {:pricing ...}} remain
valid for preserving old listings.
Multi-Plan Catalogs
Use a plan catalog when a buyer should choose among multiple pricing options
for the same app, for example:
- monthly vs annual subscription
- starter vs pro tiers
- subscription plus prepaid run pack options
- a recommended default plan with alternatives
Rules:
- each plan must normalize to a unique stable
plan-id - at most one plan can be marked
defaultorrecommended - existing buyer entitlements store the selected
plan-id, so deleting or renaming active plan ids is risky and should be treated like a migration - Breyta does not infer a billing plan from runtime form input values. A
:before-runapp with multiple plans uses the selected checkout/install plan,
or the default visible plan when no explicit plan is supplied. If a run form
value should determine the charged tier, model that as separate public plans
with an explicit plan selection surface; do not rely on matching a field value
such as"deep"to aplan-idof"deep".
Example:
{:slug :paid-flow-catalog
:tags ["catalog"]
:discover {:public true}
:marketplace {:visible true
:app {:app-id "paid-flow-catalog"
:app-name "Paid Flow Catalog"
:app-primary-flow-slug "paid-flow-catalog"
:app-flow-slugs ["paid-flow-catalog"]
:monetization
{:plans [{:plan-id "starter"
:name "Starter"
:default true
:pricing {:type "subscription"
:amount 29
:currency "usd"
:interval "month"}}
{:plan-id "pro-pack"
:name "Pro Pack"
:pricing {:type "usage"
:amount 100
:currency "usd"
:unit "run"
:included-quantity 500}}
{:plan-id "metered"
:name "Metered"
:pricing {:type "subscription-usage"
:amount 49
:currency "usd"
:interval "month"
:unit "run"
:included-quantity 22
:free-quantity 5}}]}}}
...}
If you operate directly against /api/commands, the underlying monetization command is flows.marketplace.monetization.update.
For source-authored paid apps, prefer the app-owned catalog shape above. For
day-to-day creator work, prefer the Publish page so public listing,
monetization, and draft publishing stay in one place.
Publish Checklist
App path:
- Confirm seller payouts are ready on
/<workspace-id>/billing. - Open
/<workspace-id>/flows/<slug>/publish. - Save the public listing and paid-flow settings.
- Publish changes so the current draft becomes the live buyer version.
- Verify from another workspace.
Source/CLI path:
- Author the flow with descriptive metadata tags for the use case, audience,
integration, and modality. Do not treatend-useras an installability
requirement; existing legacy flows may still carry it as ordinary metadata. - For a new paid app, add
:marketplace {:app {... :monetization {:plans [...]}}}for paid behavior.
Keep:marketplace {:monetization ...}only when preserving an existing
legacy listing. - Choose one public visibility path:
- source-first: add both
:marketplace {:visible true}and
:discover {:public true}in the flow file before push - explicit after release: run
breyta flows public publish <slug>
- source-first: add both
- Push the flow:
breyta flows push --file ./tmp/flows/<slug>.clj
- Release a live version:
breyta flows release <slug> --release-note-file ./release-note.md
-
Run
breyta flows public publish <slug>if you chose the explicit path. -
Verify the buyer surface from another workspace.
Buyer Install And Invocation Path
Use the web Discover/public app surface for the paid entitlement step. The
supported buyer entry points are:
/<buyer-workspace-id>/discoverhttps://breyta.ai/apps/<flow-slug>
For a normal free public app, the install CTA creates the installation and then
opens installation setup. For a paid public app, the CTA first reflects the paid
state:
| Paid state | CTA | What happens before install |
|---|---|---|
| subscription or subscription usage | Subscribe | Stripe Checkout starts the subscription or confirms the trial |
| one-time purchase | Purchase | Stripe Checkout completes the purchase |
| prepaid usage pack | Buy runs | Stripe Checkout buys the run pack |
| no-card trial | Start trial | Breyta creates the trial entitlement without charging a card |
When the paid flow uses a :before-install paywall, Breyta must have an
entitlement before it creates the installation. After checkout or trial entry,
Breyta opens the installation setup surface:
/<buyer-workspace-id>/flows/<flow-slug>/installations/<installation-id>?configure=setup
The buyer completes required setup inputs and connection bindings there, then
opens the installed app page to run it:
/<buyer-workspace-id>/flows/<flow-slug>/installations/<installation-id>
If the flow uses a :before-run paywall, installation can exist before payment,
but run and installed-interface calls are blocked until checkout or entitlement
recovery completes.
CLI installation support starts after the paid entitlement exists, or for free
public apps that do not need entitlement setup. If a paid app offers a no-card
trial, start it from Discover or the public app checkout action first, then use
the CLI to create/configure/enable the installation and to call installed
interfaces:
breyta flows installations create <flow-slug> --name "Client install"
breyta flows installations configure <installation-id> --input '{"setup":"value"}'
breyta flows installations enable <installation-id>
breyta flows installations interfaces <installation-id>
breyta flows interfaces call <flow-slug> <interface-id> \
--installation-id <installation-id> \
--input '{"key":"value"}' \
--wait
If a CLI command hits a missing or expired paid entitlement, treat the returned
checkout action as the recovery path. Complete that web checkout, then rerun the
installation or interface command.
Buyer Payment Method Reuse
Paid public-app Checkout stores one shared marketplace Stripe Customer on the
buyer workspace at billing/state.marketplace.stripeCustomerId. Later paid app
Checkout Sessions for the same buyer workspace pass that Customer to Stripe, so
Stripe can redisplay eligible saved payment methods across paid Breyta apps.
Breyta writes this shared Customer from both completion paths:
- the browser return path after Checkout completes
- the Stripe
checkout.session.completedwebhook path after the paid
entitlement leaf is written
Entitlement leaves still keep their own customerId for audit and recovery. If
an older paid entitlement exists but the shared marketplace Customer is missing,
the next paid-app Checkout attempts a lazy repair from the newest completed paid
entitlement before creating the Stripe Checkout Session.
Stripe only redisplays payment methods that are eligible for redisplay. In
practice, the buyer must have saved the method during Checkout or otherwise made
it reusable. If the card was not saved or is not redisplay-eligible, Checkout
can still ask for payment details even when Breyta passes the shared Customer.
Programmatic callers use the installed-interface endpoint, not the source
flow's normal draft/live run URL:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
/api/workspaces/{buyer-workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
MCP-capable agents normally use the workspace MCP server:
search_flow_tools, inspect_flow_tool, call_flow_tool with
installation_id, then get_run_status. A direct installed Streamable HTTP
MCP endpoint remains available when the caller deliberately wants per-flow
isolation.
Verify The Published Result
From the Publish page, check that:
- seller payouts are ready for paid flows
- availability is public
- pricing and trial settings match the intended buyer offer
- paid checkout is before install
- the live version is up to date
From the CLI, use:
breyta flows show <slug> --pretty
breyta flows discover list
breyta flows discover search <query>
breyta flows readiness <slug> --target live --public --marketplace
Check for:
tagsare descriptive metadata; installability comes from public visibility,
marketplace visibility, and a released live version, not from anend-usertag.marketplace.visibleistruemarketplace.app.monetization.planscontains the intended pricing, paywall,
and optional trial fields for new paid apps. Existing legacy listings may
still reportmarketplace.monetization.discover.publicistrue- the flow has an active released version
- the public app page at
https://breyta.ai/apps/<flow-slug>opens the
intended buyer-facing app surface
Then verify the actual buyer experience from another workspace in the web app:
- the flow appears in
/:workspace-id/discover - the CTA matches one-time vs subscription vs trial behavior
- checkout completes
- the resulting installation state matches the paywall policy
Current Product State
The current publishing flow is real and creator self-serve.
Today:
- seller onboarding starts from the workspace billing page
- paid-flow publishing starts from the flow Publish page
- the Publish page writes listing, pricing, trial, and live publish state
- CLI/source metadata still works for scripted or code-reviewed publishing
- paid publishing is gated by
BREYTA_FLOW_REUSE_APPROVER_USER_IDSduring rollout
Treat the app path as canonical for normal creator use, and the CLI/source path
as an advanced/operator path.