Installations (End-User Flows)
Quick Answer
Use this guide to model, create, enable, and operate end-user flow installations with installation-scoped inputs and uploads.
An "end-user flow" is a flow intended to be used by others in the workspace.
Public discover visibility and released installable interfaces gate discover and
install surfaces.
An "installation" is a per-user instance of an end-user flow, backed by an internal
runtime target owned by the subscribing user.
Public Discover Visibility
If an end-user flow should appear in discover/install surfaces, set public discover visibility explicitly.
Before enabling it, ask the flow author to confirm that the flow may be made
accessible to all Breyta users and is ready for end-user installation.
Authoring options:
{:discover {:public true}
...}
or after push:
breyta flows public publish <slug>
Requirements:
- public visibility must be enabled for marketplace and Discover surfaces
- the flow must have an active released version
Checklist to make a flow show up in Discover:
- Choose one visibility path:
- Source-first: add both
:discover {:public true}and:marketplace {:visible true}before push only after explicit approval. - Explicit after release: run
breyta flows public publish <slug>after push and release, only after explicit approval.
- Source-first: add both
- Push the flow with
breyta flows push --file .... - Release/promote it so there is an installable live version.
- If you chose the explicit path, run
breyta flows public publish <slug>after release. - Verify the web Discover install dialog. For CLI indexing checks, use another
workspace or--include-ownonly while debugging the owner workspace. - For paid reusable apps, keep Discover/search metadata current before relying
on template-adjacent reuse suggestions: tags should name the output,
business function, modality, paid/installable status, and callable surface;
then run the normal search reindex/backfill operation for the touched app.
Webhook-interface flows can be installable when auth and setup are explicit.
For public flows, keep a manual interface available while validating webhook or
schedule behavior.
Use breyta flows show <slug> --pretty to confirm stored metadata includes discover.public.
Use breyta flows discover list or breyta flows discover search <query> to browse the same public installable catalog the web app shows.
This is different from breyta flows search <query>, which searches actual workspace flow metadata, and from breyta flows templates search <query>, which searches approved reusable templates to inspect and copy from.
For app requests, show a visible reuse checklist before source edits, direct
provider setup, or template duplication: needed outputs/functions; Discover
searches tried; relevant paid apps found; and selected/rejected paid apps with
reasons. Search relevant Discover public and paid installable apps by requested
outcome, business function, and output modality. For multimodal apps, check
every needed function. For example, check text-generation apps,
image-generation apps, video-generation apps, and any relevant paid app
separately before designing a wrapper flow. For an agent that creates ads,
example searches include ad generator, copywriting, text-generation,
OpenAI image generation, image-generation, and social ad creative.
For paid public flows and Stripe seller onboarding, see Paid Public Flows.
Activation Vs Install
The activation/setup URL and the Discover install surface are different.
| Surface | Proves | Does not prove |
|---|---|---|
/activate or configure/check | The owner workspace or selected runtime target has required bindings and setup inputs. | That an end user can discover, install, configure, and run the flow. |
| Discover install dialog | The flow is exposed as an installable end-user flow. | That the installed target has all installer-owned connections and inputs bound. |
| Installation run | The installer-scoped target can execute with its saved bindings and inputs. | That public copy, pricing, or media were reviewed. |
For installable-flow work, do not stop at /activate. Verify the full end-user
path: release/promote the live version after approval, open the Discover install
dialog when browser access is available, create/configure/enable an installation,
bind installer-owned connections and setup inputs, inspect run readiness, and
run the installed target:
breyta flows installations create <slug> --name "Smoke install"
breyta flows installations configure <installation-id> --input '{"<field>":"<value>"}'
breyta flows installations enable <installation-id>
breyta flows installations get <installation-id> --pretty
breyta flows run <slug> --installation-id <installation-id> --wait
When <slug> comes from breyta flows discover list or
breyta flows discover search <query>, the CLI resolves a unique public
Discover match from the source workspace automatically. If the slug is
ambiguous across public sources, pass the Discover source fields explicitly:
breyta flows installations create <slug> --source-workspace-id <source-workspace-id> --source-flow-slug <slug>
Authors testing their own paid/public source app through Buyer Test Mode should
run the install command from the Buyer Test workspace and make the source install
intent explicit. --buyer-test is an installation-scoped CLI intent guard for
the run command; the server derives Buyer Test behavior from the current
workspace and installation, so do not combine it with --target.
breyta flows installations create <slug> \
--buyer-test-source-install \
--source-workspace-id <source-workspace-id> \
--source-flow-slug <slug>
breyta flows run <slug> --buyer-test --installation-id <installation-id> --wait
For paid public apps, the Discover/public app CTA owns the payment or trial
entry step. A before-install paywall routes through checkout first, then opens
the installation setup page for the created installation:
/<buyer-workspace-id>/flows/<flow-slug>/installations/<installation-id>?configure=setup
After setup, call the installed app from its installation page, with
breyta flows interfaces call ... --installation-id <installation-id>, or with
the installed interface API endpoint. If the CLI/API returns an entitlement or
checkout action, complete that web checkout before retrying the install or run
command.
Generic paid-app wrapper pattern:
- Parent flow collects the user's inputs and any installed app ids.
- Install/configure the relevant paid app in the caller workspace.
- Check
breyta flows installations get <installation-id> --prettyand
breyta flows installations interfaces <installation-id>before wrapper
design. - Parent flow calls the installed app with
flow/call-flowand
:installation-id. - Parent flow transforms that output into the next app's input when another
relevant paid app should be chained. - Parent flow returns a polished combined output and links any generated
resources.
Public app reuse example:
breyta flows discover search "reviewed ai response text generation" --limit 5 --pretty
# If Discover or https://breyta.ai/apps/reviewed-ai-response-generator returns
# checkout, trial, or subscription entry, complete that web step first.
breyta flows installations create reviewed-ai-response-generator --name "Reviewed AI response" --pretty
breyta flows installations get <installation-id> --pretty
breyta flows installations interfaces <installation-id> --pretty
breyta flows interfaces call reviewed-ai-response-generator generate-reviewed-response-api \
--installation-id <installation-id> \
--input '{"intended_audience":"founders evaluating workflow automation tools","use_case":"concise product explanation","task":"Write a two-sentence explanation of why reviewed AI output reduces editing time."}' \
--wait --pretty
Expected success shape after checkout/entitlement/setup is complete:
runReadiness.ready is true before the call, the interfaces list includes an
installation-targeted HTTP or MCP interface, and the call returns ok: true, a
terminal completed run/workflow id, and an outputWebUrl with the generated
result. If readiness reports
billing_subscription_required, billing_trial_ended, invalid_flow_configuration,
an entitlement block, or missing bindings, fix that first instead of building a
wrapper around a blocked installation.
If browser access is unavailable, keep the CLI installation proof and report
web UI not verified.
Install Surface Copy
Installable flows can expose separate install-facing markdown copy from their normal flow description.
Use:
breyta flows update <slug> --publish-description-file ./publish-description.md
or:
breyta flows update <slug> --publish-description "## Install this flow"
Behavior:
| Surface | Copy source |
|---|---|
| Discover/install dialog | publishDescription when set |
| Discover/install dialog fallback | normal flow description when publishDescription is empty |
| Discover card/list preview | normal flow description |
| Normal setup/run page | normal flow description |
This lets you keep a short catalog summary while showing richer installation guidance in the dialog itself.
Normal setup and run pages render the flow description as sanitized Markdown.
Use this for operational guidance that must stay visible after install, such as
opening a related setup flow before running:
{:description "Open [Voice Profile Writer](/workspace/flows/voice-profile-writer/launch) before running outreach."}
Raw https://... URLs are also autolinked. Unsafe HTML, scripts, and event
handler attributes are stripped before rendering.
Discover Card Media
Public discover/install cards can also show curated image or video media:
breyta flows update <slug> \
--publish-media-type image \
--publish-media-source-kind https-url \
--publish-media-source https://cdn.example.com/hero.png \
--publish-media-alt "Preview of the generated result"
Notes:
publishMediareplaces the whole stored discover card media value- Use
--clear-publish-mediato remove it - For video, add
--publish-media-poster-kindand--publish-media-posterfor an optional poster - If you keep flow metadata in source control, you can also author the same value as
:publish-mediain the flow file and push it
Core Concepts
| Term | Meaning |
|---|---|
| End-user flow | Flow intended for subscribers. |
| Installation | Per-user runtime target instance for an end-user flow. |
| Activation inputs | Setup values the installer enters once and reuses on later runs. Installers can change them by reconfiguring setup. |
| Promote/release behavior | flows promote/flows release advance live and track-latest installations, but do not clear previously configured setup values. |
Creator: declare worker requirements
Use {:kind :worker ...} inside :requires when a flow depends on external workers.
Examples:
{:requires [{:kind :worker
:label "Security review workers"
:provided-by :author
:capability "codex-agent"
:job-types ["codex-security-review" "codex-security-fix"]
:provider "breyta/jobs"
:version "v1"}]}
{:requires [{:kind :worker
:label "Customer sync worker"
:provided-by :installer
:capability "customer-sync"
:job-types ["customer-sync"]
:provider "acme/customer-sync-worker"
:setup-summary "Deploy the packaged worker before enabling this installation."
:setup-docs-url "https://docs.example.com/customer-sync-worker"}]}
Install behavior is derived from those requirements:
- no worker requirements: native runtime only
- author-provided worker requirements: managed external workers without extra installer setup
- installer-provided worker requirements: packaged worker/runtime required before launch
- prefer
:job-typesin authored worker requirements;:job-typeis accepted as a single-type alias
Rule of thumb:
- put installer-owned config and setup fields in
:requires - put external worker dependencies in
:requiresas{:kind :worker ...}
If installers must run a worker, provide a packaged deployment story and setup docs rather than raw jobs protocol details.
Creator: declare activation inputs (setup form)
Installation setup inputs are declared via :requires with a {:kind :form ...}
requirement. The installer fills them out once during setup and the values are
reused on later runs until the installation is reconfigured.
Use :invocations when an input must be provided on each manual run instead of
being saved on the installation. Invocation inputs do not block setup; they
render directly in the launch/run form.
Use :provided-by :author on a requirement when it is satisfied by the flow
author/workspace owner instead of the installation user. Author-provided
requirements do not block end-user setup or /launch.
Creator: declare installer-owned OAuth connections
For delegated accounts that the installer must connect, declare an installer-owned
:http-api slot with an :oauth contract. The setup sidepeek renders that slot
as a connect-first account card, with a fallback selector for existing workspace
connections. For custom OAuth, the author defines the provider template on the
slot; setup renders a connect button for that provider but does not let
installers create or edit custom OAuth metadata.
{:requires [{:slot :crm
:type :http-api
:provided-by :installer
:label "CRM account"
:base-url "https://api.example.com"
:oauth {:provider :any
:scopes {:required ["contacts.read"]}
:requires-refresh-token true}}]}
Author-defined custom provider example:
{:requires [{:slot :tiktok
:type :http-api
:provided-by :installer
:label "TikTok account"
:base-url "https://open.tiktokapis.com"
:oauth {:provider :custom
:provider-name "TikTok"
:template-connection-id "conn-tiktok-template"
:scopes {:required ["user.info.basic"]}
:requires-refresh-token true}}]}
Connection slots are required by default. Mark a slot optional with
:optional true; :required false is also accepted as the inverse setup alias.
Optional slots still appear in setup so installers can connect them, but they do
not block activation or launch when left unbound. The flow should only execute
steps for an optional slot after checking that the corresponding binding exists.
When the installer clicks a custom OAuth template button, Breyta creates a
pending installer-owned connection from the author template, clears token fields,
and redirects to the provider login page. When the installer completes OAuth
from setup, Breyta saves the concrete connection binding on that installation
before returning to setup.
Authors do not need to authorize their own provider account to create the
template; use Save for installers after entering provider metadata and
client credentials. Use Save and connect only when the author also wants a
test connection in the author workspace.
For public flows, the cloned connection and token secrets live in the
installer's workspace. Runtime uses the installation binding to read and refresh
that installer-owned connection; it does not copy installer tokens into the
creator workspace. If an older public installation created a custom OAuth
connection in the creator workspace, have the installer reconnect the slot from
setup so the connection is recreated in the installer workspace.
Setup GET routes remain read-only, and OAuth return paths must be relative
workspace paths.
Template authorization is release-version based. The template connection is
usable by installers only when the installed flow version declares that exact
:template-connection-id on the requested :requires slot. There is no separate
connection-level publish/share flag today; after changing a template ID or slot
OAuth metadata, release a new flow version and update or recreate installations
so the setup sidepeek and OAuth connect handler read the same pinned version.
Some custom providers use non-standard authorize URLs or token parameter names.
Keep the provider out of the registry and put the mapping on the custom OAuth
template connection config. Use :authorize-params for the provider login URL,
:scope-separator when the provider does not accept space-separated scopes,
:token-params for the authorization-code exchange, and
:refresh-token-params for refresh. Keep scopes as discrete scope values on the
connection; Breyta joins them with the configured separator when it builds the
authorize URL. Custom OAuth account switching is also provider-specific; add the
provider's documented account chooser prompt to the template's extra authorize
params when required. For example, providers that require client_key instead of
client_id and comma-separated authorize scopes can use:
{:oauth {:authorize-params {:client-id "client_key"}
:scope-separator ","
:token-params {:client-id "client_key"}
:refresh-token-params {:client-id "client_key"}}}
Only enable PKCE when the provider's web OAuth docs require it. Some providers
support PKCE only for mobile or desktop clients, while their web token exchange
rejects code_verifier. In that case set :oauth {:pkce {:enabled false}} on
the template connection before installers connect.
After changing authorize or token parameter mappings, release a new flow version
and have installers reconnect the slot so Breyta creates a fresh installer-owned
connection and stores a usable token secret.
Example:
{:requires [{:kind :form
:label "Setup"
:fields [{:key :region
:label "Region"
:field-type :select
:required true
:options ["EU" "US"]}]}]}
{:invocations {:default
{:label "Run input"
:inputs [{:name :question
:label "Question"
:type :text
:required true}]}}}
{:requires [{:slot :ai
:type :http-api
:label "LLM Provider"
:auth {:type :api-key}
:provided-by :author}]}
If you are maintaining an older flow that uses run-collected form requirements,
:label customizes the recent-runs heading and :title customizes each recent
run row. New flows should put per-run fields in :invocations.
Example:
{:requires [{:kind :form
:collect :run
:label "Open questions"
:title :question
:fields [{:key :question
:label "Question"
:field-type :text
:required true}]}]}
Behavior:
:labelchanges the section heading fromPrevious runs/Recent runsto your chosen text.:titlechanges each recent-run row title. Use a keyword to pull from a field in the run input, or a string for fixed text.- When
:titleresolves to a value, the recent-run row uses that title with the run timestamp. It does not append fallback resource-count copy. - If
:titleis not set, completed recent-run rows keep the fallback title such as1 resource created. :titleis only for the run list. Viewer envelopes can include:breyta.viewer/options {:title ...}for viewer metadata and grouped item labels, but a single final output page or panel keeps platform chrome such asFinal output. The canonicalflow-outputresource name is platform-managed.- Keep run titles, viewer titles, and persisted resource names as separate naming surfaces.
Supported setup form field types:
:field-type | What the installer sees | Typical use |
|---|---|---|
:text | Single-line text input | IDs, names, short prompts |
:textarea | Multi-line text box | Longer instructions or prompts |
:select | Dropdown | Region, mode, model choice |
:boolean | Checkbox | Confirm/enable flags |
:number | Numeric input | Limits, counts, thresholds |
:date | Date picker | Scheduled/reporting dates |
:time | Time picker | Clock time inputs |
:datetime | Date-time picker | Combined schedule inputs |
Invocation resource input notes:
- Use invocation input
:type :resourcefor new flows. - The run UI shows
Add resourcesfor existing workspace resources only when a resource input is declared. - Local upload is available on the run page as a separate
Upload file from computerorUpload files from computeraction when the field allows:fileresources.:fileis the default resource type. - Files uploaded from the run page are stored as normal workspace
:fileresources and auto-selected for the current run, so they also appear in later picker searches. - Use
:multiple truewhen the flow expects a collection of resources. - Resource fields default to
:file, which covers uploads and persisted blobs. - Use
:resource-typesand:acceptonly when you need to narrow what the user can select. :acceptsupports exact MIME types like"application/pdf"and wildcard prefixes like"text/*". The same allowlist is used for both picker filtering and the browser file chooser on local uploads.- When both
:resource-typesand:acceptare present, the selected resource must satisfy both filters. - For single-value resource fields, a local upload replaces the current selection. With
:multiple true, new uploads are added to the current selection.
Example:
{:invocations {:default
{:label "Run input"
:inputs [{:name :resources
:label "Resources"
:type :resource
:required true
:multiple true
:accept ["application/pdf" "text/plain"]}]}}}
Text-focused example:
{:invocations {:default
{:label "Run input"
:inputs [{:name :resources
:label "Text resources"
:type :resource
:required true
:multiple true
:accept ["text/*"
"application/json"
"application/xml"
"application/edn"]}]}}}
Manual upload recipes for installed flows
Use resource invocation fields for normal manual installed runs. This renders a
resource picker plus local upload action on the installed run page:
{:invocations {:default
{:label "Analyze images"
:inputs [{:name :images
:label "Images"
:type :resource
:required true
:multiple true
:accept ["image/*"]}]}}
:interfaces {:manual [{:id :run
:label "Analyze images"
:invocation :default
:enabled true}]}}
Use webhook file inputs when the installed flow should receive multipart file
uploads from an integration or from the CLI installations upload command:
{:invocations {:default
{:inputs [{:name :files
:type :file
:required true
:multiple true}]}}
:interfaces {:webhook [{:id :file-upload
:invocation :default
:event-name "files.upload"
:auth {:type :hmac-sha256
:header "X-Signature"
:secret-ref :webhook-secret}}]}}
For older flows, {:kind :form :collect :run ... :field-type :resource} and
event triggers with :config {:fields [{:name :files :type :file ...}]} remain
compatibility shapes. New source should prefer :invocations plus explicit
:interfaces.
Inside the flow: how setup and invocation fields appear in input
Setup form fields and invocation inputs become part of flow/input.
- setup fields are available on every run after setup is saved
- invocation inputs are available only for the current run
- if both exist, the flow sees one combined input map
Example:
{:requires [{:kind :form
:fields [{:key :region
:field-type :select
:required true
:options ["EU" "US"]}]}]
:invocations {:default
{:inputs [{:name :question
:type :text
:required true}
{:name :resources
:type :resource
:required true
:multiple true}]}}
:flow
'(let [input (flow/input)
region (:region input)
question (:question input)
resources (:resources input)]
{:region region
:question question
:resource-count (count resources)})}
The run input seen by the flow looks like:
{:region "EU"
:question "What changed since last week?"
:resources [{:type :resource-ref
:uri "res://v1/ws/ws-1/file/report-a"}
{:type :resource-ref
:uri "res://v1/ws/ws-1/result/summary-b"}]}
Creator: declare per-run inputs for manual and webhook interfaces
For manual and webhook runs, declare per-run inputs in :invocations. Point
manual interfaces at the invocation, and expose webhook ingress through
:interfaces :webhook.
For manual interfaces, the interface :label is reused as the primary
manual-run CTA on flow, installation, launch, and setup surfaces.
For webhook interfaces, the interface owns delivery details such as
:event-name, auth, replay, and endpoint metadata. Older webhook definitions
remain accepted for existing flows, but new webhook ingress should be moved to
:interfaces :webhook.
Older manual field definitions remain accepted for existing flows, but new
manual-run fields should be written in :invocations.
Invocation fields are validated and can be used to:
| Use | Details |
|---|---|
| Run form UX hints | Tell UI/CLI which inputs to request. |
| Resource/file input shape | Enable resource selection or multi-file upload with :multiple true. |
Example (manual run):
{:invocations {:default
{:inputs [{:name :question
:type :text
:label "Question"
:required true}]}}
:interfaces {:manual [{:id :ask
:label "Ask"
:invocation :default
:enabled true}]}}
Example (webhook upload):
{:invocations {:default
{:inputs [{:name :files
:type :file
:required true
:multiple true}]}}
:interfaces {:webhook [{:id :file-upload
:invocation :default
:event-name "files.upload"
:auth {:type :hmac-sha256
:header "X-Signature"
:secret-ref :webhook-secret}}]}}
Notes:
| Note | Behavior |
|---|---|
:file, :blob, and :blob-ref field types | Normalized as :blob-ref in validation. |
| Multipart ingestion | Flows-api persists file parts and passes blob-ref maps (for example {:path \"...\" :size-bytes 123 ...}). |
Creator: schedule setup controls
Top-level schedules bind one cron expression to the authored flow version.
That is not enough for public paid flows where each installer expects to choose
whether the flow runs weekly, monthly, in their timezone, or not at all.
Every schedule renders installer-facing setup controls. The schedule's :cron
and :timezone are the flow defaults:
{:schedules [{:id :weekly-review
:label "Scheduled security review"
:invocation :default
:enabled false
:cron "0 9 * * MON"
:timezone "UTC"}]}
Author setup can save author schedule overrides. Those overrides become the
inherited default for installers. Installer setup then stores installation-owned
schedule settings without mutating the authored schedule.
Current behavior:
| Field | Behavior |
|---|---|
| Schedules | Render schedule controls inside setup. |
| Saved choices | Stored as installation schedule settings. New installed schedules start disabled until enabled from setup, schedule card, or CLI. |
| Author setup | Author setup uses the same controls. Reset removes the author override and restores authored defaults. |
| Time/day overrides | Daily/weekly/monthly cadences expose bounded time/day controls. Monthly last uses a guarded 28-31 cron window. |
| Timezone control | Setup shows common IANA zones. Saved/default zones win; detected zones fill gaps. |
| Source schedule config | Setup does not rewrite authored :cron or :timezone; schedule sync/runtime must consume the saved schedule settings when applying installer-specific schedules. |
| Zero-setup install | Schedule controls count as installer setup for automatic runs. The installed flow page shows a schedule info card instead of a global activation action. |
| Installation runability | enable / disable guards all installation runs. Saved setup and per-interface/schedule settings are preserved. |
| Automatic execution state | Schedules/webhooks also have per-surface enablement. Authored :enabled false stops that surface. |
| Webhook interfaces | Setup renders one switch per webhook interface. On materializes/resumes; off disables that endpoint. |
Authors can update their own default schedule overrides from the CLI:
breyta flows configure my-flow \
--schedules '{"weekly-review":{"enabled":false,"preset":"monthly","timezone":"Europe/Oslo","hour":14,"minute":30,"dayOfMonth":"last"}}'
To change only enablement, use the shorthand flags:
breyta flows configure my-flow --schedule-disable weekly-review
breyta flows configure my-flow --schedule-enable weekly-review
To remove an author override and inherit the flow schedule default again:
breyta flows configure my-flow --schedule-reset weekly-review
Installers can update an installation schedule the same way:
breyta flows installations configure <installation-id> \
--schedules '{"weekly-review":{"enabled":true,"preset":"weekly","timezone":"Europe/Oslo","hour":9,"minute":0,"dayOfWeek":"MON"}}'
They can also enable, disable, or reset one schedule:
breyta flows installations configure <installation-id> --schedule-enable weekly-review
breyta flows installations configure <installation-id> --schedule-disable weekly-review
breyta flows installations configure <installation-id> --schedule-reset weekly-review
Use the schedule id as the schedule key. The accepted fields are enabled,
preset (daily, weekly, or monthly), timezone, hour, minute,
dayOfWeek, and dayOfMonth (first, mid-month, or last).
Recommended launch rule: use this for flows where schedule choices are part of
setup. For paid public flows, keep a manual run path available while validating
the schedule runtime path for installer-specific timezone, pause/resume, and
failure visibility.
End user: subscribe + configure automation (CLI)
| Step | Command |
|---|---|
| 1. Create installation | breyta flows installations create <flow-slug> --name "My setup" |
2. Provide activation inputs from :requires form fields | breyta flows installations configure <installation-id> --input '{"region":"EU"}' |
| 3. Enable the installation when setup is ready | breyta flows installations enable <installation-id> |
| 4. Inspect manual/HTTP/webhook/MCP invocation interfaces | breyta flows installations interfaces <installation-id> |
| 5. Run the installed app | breyta flows run <flow-slug> --installation-id <installation-id> --input '{...}' --wait |
6. List your runs (each row includes its installationId) | breyta runs list --installation-id <installation-id> |
| 7. Inspect a run and read its output | breyta runs inspect <workflow-id> --installation-id <installation-id> |
This is the end-to-end path for using an app published by another creator,
not just one you authored yourself: discover (breyta flows discover search),
install, configure, enable, run, then inspect and read the output.
Pass --installation-id when inspecting an installed run. breyta runs inspect,
breyta runs events, and breyta runs step resolve an installed run only when
the installation is supplied; without it they return Run not found (404) by
design, because a run can map to more than one installation. The installationId
is in every runs list row and in the runs.start/run response, so pass it back
to runs inspect.
As the installer you see your run's status, timing, final output, and
artifacts — the same view a person gets on the run page in the web app. The
flow's internal steps are the creator's and are not exposed to installers at
all: runs inspect/runs events/runs step return no step list, step
previews, or step captures for an installed run (the response marks
stepsRedacted). Read the final output and any generated artifacts with
breyta resources read <resource-uri> (artifact res://... refs appear in the
run's output).
Creating or configuring an installation does not opt the installer into runs or
automatic execution. Manual runs, interface calls, and schedules are accepted
only after the installation is enabled. Automatic execution still also requires
the relevant webhook interface or schedule to be enabled in Setup, from the
installed flow page, or through the CLI/API schedule and interface controls.
Schedule controls count as setup for automatic runs because the installer must
explicitly choose whether each schedule is on, plus its frequency and timezone.
When automatic trigger enablement is requested before required setup is complete,
the API returns setup-state details that identify missing setup fields, missing
connection/blob-storage bindings, or invalid connection-backed choices.
If the installed flow declares an HTTP interface, smoke-test it with:
breyta flows interfaces call <flow-slug> <http-interface-id> \
--installation-id <installation-id> \
--input '{"key":"value"}' \
--wait
Without --wait, accepted long-running interface calls include data.statusUrl
when a workflowId is available. That status URL stays scoped to the same
installation/interface endpoint.
Installed interface endpoints use this shape:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
The workspace-scoped form
/api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
remains available as an alternate compatibility URL.
Use an installed public flow from another flow or agent
Install the public flow first when it has installer-owned setup, billing,
connections, or saved activation inputs. After install, choose the call surface
by caller:
| Caller | Use | Notes |
|---|---|---|
| Authored Breyta flow, one installed child | flow/call-flow with :installation-id | Native child-flow composition. |
| Authored Breyta flow, many installed children | :fanout with {:type :call-flow ... :installation-id ...} | Bounded parallel child-flow spawn/collect. |
| Claude, Codex, Cursor, or another MCP-capable agent | Workspace MCP search_flow_tools, inspect_flow_tool, call_flow_tool, then get_run_status | Pass installation_id when targeting a specific installed app instance. |
| External non-MCP system | Installed HTTP interface | Use the installation-scoped endpoint and bearer auth. |
| Manual smoke test or debug run | CLI/API flows run --installation-id | Control-plane/debug path, not the default wrapper-flow composition path. |
flow/call-flow is the Breyta flow DSL child composition form. call_flow_tool
is the workspace MCP broker tool for external MCP clients.
Do not make a wrapper flow call its installed child through the HTTP interface
unless you are intentionally testing the external HTTP transport; the native
wrapper-flow path is flow/call-flow with :installation-id.
Typical install flow:
- Find the public flow in Discover or with
breyta flows discover search. - Create, configure, and enable an installation.
- Inspect callable surfaces with:
breyta flows installations interfaces <installation-id>
- Inspect runtime readiness before composing a wrapper around the install:
breyta flows installations get <installation-id>
For paid public apps, the response includes data.runReadiness. It mirrors the
same gate the run itself uses, so an app with free trial runs available reports
runReadiness.ready: true and can be run without any billing step. A blocked
value, such as public_flow_trial_runs_exhausted (free trial used up) or a
card-upfront paid app, sets runReadiness.ready: false and returns a
billingUrl/checkoutPath; complete that checkout action before wrapper runs,
HTTP calls, or MCP calls can succeed. A reason of installation_unavailable
(no billing block) means the source app is currently unavailable (for example
disabled or unpublished), not a billing issue. A reason of
invalid_flow_configuration means the app has an authored step-configuration bug
that would fail every run (for example a step value over its allowed range);
billing and setup cannot fix it, so do not retry or build a wrapper — contact the
app owner to fix the app.
If manual QA from the installed app page appears to "lose" earlier runs when a
new run starts, inspect the source flow's :concurrency before assuming the
installed UI is broken. {:type :singleton :on-new-version :supersede}
intentionally replaces the earlier in-flight run. Use :coexist when the
installed app is supposed to allow multiple user-started runs without replacing
the earlier one.
- Smoke-test the installed interface:
breyta flows interfaces call <flow-slug> <http-interface-id> \
--installation-id <installation-id> \
--input '{"prompt":"A clean product image","quality":"low"}' \
--wait
Inside an authored Breyta wrapper flow, call the installed flow as a child:
'(let [input (flow/input)
reviewed (flow/call-flow :reviewed-ai-response-generator
{:prompt (:prompt input)}
{:installation-id "installation-id-from-setup"})]
{:reviewed reviewed})
flow/call-flow uses positional arguments: child flow slug, child input map,
then optional child call options. Put :installation-id in the third map. Do
not use a one-map shape like
(flow/call-flow {:flow-slug ... :installation-id ... :input ...}).
For a bounded batch of installed child runs, use :fanout with :call-flow
items:
(flow/step :fanout :review-prompts
{:items (mapv (fn [prompt]
{:type :call-flow
:flow-slug :reviewed-ai-response-generator
:installation-id "installation-id-from-setup"
:input {:prompt prompt}})
prompts)
:max-concurrency 5
:on-error :collect})
For external non-MCP clients, use the installed HTTP endpoint:
/api/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
The workspace-scoped alternate is also accepted:
/api/workspaces/{workspace-id}/flows/{flow-slug}/installations/{installation-id}/interfaces/{interface-id}
HTTP clients should send the normal workspace/API authorization header:
Authorization: Bearer <BREYTA_TOKEN>
MCP clients can either use the workspace MCP server or a direct installed
Streamable HTTP MCP endpoint. Prefer the workspace MCP server for Claude,
Codex, Cursor, and similar agents because one workspace server can discover and
call accessible installed flow tools:
{
"flow_slug": "reviewed-ai-response-generator",
"tool_name": "generate_reviewed_ai_response",
"installation_id": "<installation-id>",
"input": {"prompt": "Draft a short response"}
}
A successful call_flow_tool starts the backing flow run and returns run
metadata for get_run_status.
Paid public flows can reject installed interface calls when entitlement or trial
runs are exhausted. Treat billing errors such as billing_trial_ended or
public_flow_trial_runs_exhausted as entitlement state, not as evidence that
the wrapper, HTTP, or MCP interface is broken. Use
breyta flows installations get <installation-id> to preflight readiness before
building or smoke-testing wrappers around a paid public app.
Inspect aggregate invocation metrics for that installation with:
breyta flows metrics <flow-slug> --installation-id <installation-id>
Metrics are redacted counters and timings for flow invocation surfaces. They do
not contain request bodies, response bodies, tokens, headers, secrets, or binding
values. Installed calls are reported with interfaceScope: "installation";
author-owned draft/live interface calls use interfaceScope: "draft" or
"live" and stay in separate aggregates.
Verify an installed-app run, and never blind-retry or cancel it
When you start an installed-app run from an agent or the CLI, the run is yours as
the installer. For a paid or trial app it reserves one unit of your usage, it
runs in your workspace context, and its final output is yours to read. The
execution itself stays owned and operated by the app's creator, so an installed
run is opaque to you: you get status, timing, and final output, never the
internal steps.
Treat the workflowId from the start response as the run handle, and reconcile
with it instead of re-running:
| Goal | Command |
|---|---|
| Confirm the run exists and see its status | breyta runs list --installation-id <installation-id> |
| Read one run's status, timing, and output | breyta runs inspect <workflow-id> --installation-id <installation-id> |
The --query installation:<installation-id> form of runs list works too.
Always pass --installation-id to runs inspect for an installed run; without
it the lookup returns Run not found (404) by design, because a run can map to
more than one installation.
A start request that returns a transient server error (for example a 502, a
500, or a gateway timeout) is not proof the run failed. The run is created
before its usage is reserved, so a transient error after the run started can
still leave a real run in your history. Do not blindly retry a side-effectful
installed run on a transient error: a blind retry can start a second run, and if
both runs complete you are charged for both. Instead:
- List runs for the installation with
runs list --installation-id <installation-id>. - If a matching run is present, take its
workflowIdandruns inspectit. The
run either already completed (read its output) or is still going (wait for it). - Start a new run only if no run was created.
Do not cancel an installed-app run. runs cancel is meant for the flow's
creator or operator, not for installers, and cancelling an installed run is not
part of the install-and-run loop. (A cancel of an installed run now returns a
clean cancelled result rather than a spurious server error, but the run still
executes in the creator's runtime workspace, so cancellation is the creator's or
operator's action, not the installer's.) You do not need
cancel for billing either: a run consumes your paid or trial allowance only when
it completes. A run that ends any other way (cancelled, failed, or timed out)
releases its reservation and is not charged. So a run that errors out or never
finishes does not cost you anything; verify its final status with
runs inspect <workflow-id> --installation-id <installation-id> instead of
trying to cancel it.
End user: upload files (CLI)
| Step | Command |
|---|---|
| 1. Inspect installation interfaces and webhook endpoints | breyta flows installations interfaces <installation-id> |
| 2. Upload one or more files to webhook interface | breyta flows installations upload <installation-id> --file ./a.pdf --file ./b.pdf |
If the webhook invocation declares exactly one field of type
file/blob/blob-ref, the CLI infers the multipart field name automatically;
otherwise pass it:
breyta flows installations upload <installation-id> --file-field files --file ./a.pdf
Installable Agent Flows
Agent-based flows have additional author/installer boundary concerns. Use
this section as a checklist when preparing an agent flow for installation.
Who provides the LLM connection?
| Pattern | :requires config | Who pays | Installer setup |
|---|---|---|---|
| Author pays | {:slot :ai :type :http-api :provided-by :author} | Author | None — connection is pre-configured |
| Installer pays | {:slot :ai :type :http-api :label "LLM Provider" :auth {:type :api-key}} | Installer | Installer enters their API key during setup |
For most installable agent flows, author-provided is simpler — the
installer never sees an API key prompt. Use installer-provided when you want
the installer to control their own model/spend.
How to handle agent memory
Agent memory uses a :table resource. In an installed flow, the memory table
must be owned by the author's workspace (it's internal working state, not
installer-visible output).
Best practice: Create the memory table in the flow itself using :persist,
and pass the resulting ref to the agent step. Do not hardcode a table URI
from your authoring workspace.
{:flow
'(let [;; The flow creates/reuses its own memory table on each run.
;; The platform routes this to the flow's workspace (author's).
memory-ref (flow/step :table :ensure-memory
{:op :schema
:persist {:type :table :name "agent-memory"}})
result (flow/step :agent :review
{:objective "Review and report."
:connection :ai
:available-steps [:files]
:memory {:table memory-ref
:auto-summarize true}
:max-iterations 8})]
result)}
What the installer sees vs. what they don't
| Concern | Visible to installer | Notes |
|---|---|---|
Flow output (:content) | Yes | The product they consume |
Output persist (:output-persist) | Yes | Blob in their workspace |
| Memory table | No | Internal to the author's workspace |
| Cost ledger | No | Flow-scoped KV, author's budget |
| Trace blob | No | Author's debugging resource |
| Evaluation scores | No | Author's quality tracking |
| Plan/checkpoint state | No | Internal to memory table |
| Connection binding | Setup only | Installer configures once, never sees internal usage |
Agent flow installation checklist
- [ ] Connection requirements declared in
:requireswith the right:provided-by - [ ] Memory table created at runtime by the flow, not hardcoded to a workspace URI
- [ ]
:output-persistused for any result the installer should access - [ ]
:costbudget set to prevent runaway LLM spend across installations - [ ] Setup form (
:kind :form) declared for any installer-visible configuration - [ ] Per-run fields declared in
:invocations - [ ] Install surface copy (
--publish-description) explains what the agent does,
not how it works internally - [ ] Tested with
breyta flows run <slug> --waitbefore release
Minimal installable agent flow example
{:slug :code-reviewer
:name "Code Reviewer"
:description "AI-powered code review for your repository."
:tags ["code-review" "ai"]
:discover {:public true}
:concurrency {:type :singleton :on-new-version :coexist}
:interfaces {:manual [{:id :run-review
:label "Run Review"
:invocation :default
:enabled true}]}
:requires [{:slot :ai
:type :http-api
:label "LLM Provider"
:provided-by :author}
{:slot :github-api
:type :http-api
:label "GitHub API"
:auth {:type :api-key}
:base-url "https://api.github.com"}]
:invocations {:default
{:label "Review input"
:inputs [{:name :repo
:label "Repository (owner/repo)"
:type :text
:required true}
{:name :branch
:label "Branch"
:type :text
:required false}]}}
:flow
'(let [input (flow/input)
repo-tree (flow/step :files :resolve-repo
{:op :resolve-source
:source {:type :git
:repo (str "https://github.com/" (:repo input) ".git")
:ref (or (:branch input) "main")
:connection :github-api}})
memory-ref (flow/step :table :ensure-memory
{:op :schema
:persist {:type :table :name "reviewer-memory"}})
result (flow/step :agent :review
{:connection :ai
:objective (str "Review the code in " (:repo input) " for issues.")
:inputs {:repo-tree repo-tree}
:available-steps [:files]
:memory {:table memory-ref
:auto-summarize true}
:cost {:enabled true
:budget {:max-tokens-total 1000000
:window-hours 720}}
:output {:format :json}
:output-persist {:name "review-report"
:content-type "application/json"}
:max-iterations 6})]
result)}
In this example:
- The author provides the LLM connection (installer never sees an API key)
- The installer provides the source-control connection and enters the repo/branch
:on-new-version :coexistkeeps installed manual runs from replacing each
other during normal user operation or QA; use:supersedeonly when
replacement semantics are intentional- Memory is created at runtime (no hardcoded URI)
- Cost budget caps total LLM spend across all installations
- Output is auto-persisted as a JSON blob the installer can access
- The flow is installable via Discover with a manual interface
Connection item selects
Use a connection item select when an installer should choose something that
belongs to one of their connected systems: a repository, channel, project,
account, folder, customer record, or similar non-secret object.
The flow author declares the connection slot and the item type. The platform
uses cached non-secret metadata from the installer-bound connection to render
the dropdown. The submitted run input is still a normal scalar value, such as an
id, slug, or path.
The dropdown does not call the connected provider while the setup or run page is
rendering or saving. Provider-specific connection setup or refresh code
populates a connection-owned item cache first. Setup/run pages then read that
cache through the Breyta connection store and API. Submitted setup and run
values are validated against the same cache, and missing connections, empty
caches, disabled items, or unknown values fail closed with actionable setup/run
errors. This keeps option fetching bounded, auditable, and independent of flow
author code.
This fail-closed behavior applies only to fields that explicitly declare a
connection-item option source. It is separate from general connection health
checks, which remain best-effort readiness signals.
Generic shape:
{:key :target
:label "Target"
:field-type :select
:required true
:options-source {:type :connection-items
:slot :work-system
:item-type "project"
:value-field "id"
:label-field "name"}}
Use :slot for the local connection requirement that backs the dropdown.
:connection is accepted as a compatibility alias for the same local slot, but
authors should not set both unless the values match. :item-type is required
and can be provider-qualified, such as "github/repository" or
"slack/channel".
value-field, label-field, and description-field map cached item payload
fields into UI options. If those are omitted, the renderer falls back to common
fields: value or id for the submitted value, label for display text, and
description for helper text.
Typical item types are connector-defined. Examples include:
| Connected system | Example :item-type | Submitted value |
|---|---|---|
| Source control | "repository" or "github/repository" | owner/repo or repository id |
| Chat | "channel" or "slack/channel" | channel id |
| Issue tracker | "project" or "linear/project" | project id |
| CRM | "account" or "crm/account" | account id |
| Storage | "folder" or "drive/folder" | folder id |
For the code-reviewer example above, the repository text field can become a
connection item select backed by the source-control connection:
{:key :repo
:label "Repository"
:field-type :select
:required true
:options-source {:type :connection-items
:slot :github-api
:item-type "github/repository"
:value-field "full-name"
:label-field "full-name"}}
The submitted run input remains the selected owner/repo string. The platform
owns option refresh and picker UX; the flow should still fail clearly if the
connection loses access before source import.
Use :workspace-flows when a setup or run field should target another flow in
the installer workspace:
{:key :target-flow
:label "Flow to update"
:field-type :select
:required true
:options-source {:type :workspace-flows}}
The submitted value defaults to the selected flow slug. Authors can use
:value-field, :label-field, and :description-field to map another field
from the flow metadata, but the default slug/name/description mapping is the
normal choice.
The picker stores a normal form value. To attach a generated cover image to the
selected flow, call :breyta with :op :flows/update, allow that operation,
and set :mutations :write. Use :publish-media {:type :image :source {:kind :flow-resource :uri <resource-uri>}} when the image was persisted by the run.
Operators can inspect the cached item types and rows behind a connection with:
breyta connections items <connection-id>
breyta connections items <connection-id> --item-type github/repository --limit 25
breyta connections items <connection-id> --item-type github/repository --raw
The default output summarizes cached rows without raw provider payloads. Use
--raw only when you need to debug provider metadata.