The root provider that wraps a built app and supplies backend config (baseUrl, tenantId, mode, appScope) to all Flowstack hooks.
provider, built-apps, Flowstack SDK, useFlowstack, BrokeredLoginButton, AuthGuard, useCollection, useAgent
<FlowstackProvider config={config}>{children}</FlowstackProvider>
import { FlowstackProvider, useFlowstack, BrokeredLoginButton } from 'flowstack-sdk'; function App() { return ( <FlowstackProvider config={{ baseUrl: 'https://sage-api.flowstack.fun', mode: 'production', appScope: '__SITE_ID__', // Hex site ID — auto-filled by build pipeline // No tenantId — derived from the brokered-login JWT. }}> <AuthGate /> </FlowstackProvider> ); }
import { FlowstackProvider } from 'flowstack-sdk'; const config = { baseUrl: 'https://sage-api.flowstack.fun', tenantId: 't_6fe54402be43', mode: 'production', jwtSecret: 'flowstack-cdn-site', passwordSecret: 'flowstack-cdn-site', }; export default function App({ children }) { return ( <FlowstackProvider config={config}> {children} </FlowstackProvider> ); }
The hook built apps use to read auth state — isAuthenticated, isInitialized, and credentials — instead of useAuth().
hook, built-apps, Flowstack SDK, BrokeredLoginButton, AuthGuard, useAuth, FlowstackProvider
const { isAuthenticated, isInitialized, credentials, setCredentials } = useFlowstack()
function AuthGate() { const { isAuthenticated, isInitialized } = useFlowstack(); if (!isInitialized) return Loading... ; // BrokeredLoginButton opens Casino's auth popup — Privy runs there, not here return isAuthenticated ? <MainApp /> : ( <BrokeredLoginButton label="Continue with Flowstack" /> ); }
function Header() { const { credentials, setCredentials } = useFlowstack(); return ( {credentials?.email} setCredentials(null)}>Sign out ); }
Read and send private messages within one DM thread in a built app.
hook, built-apps, Flowstack SDK, useThreads, sendMessage, listMessages, DmMessage
const { messages, send, isLoading, error, refresh } = useMessages(withUserKey, options?)
const { messages, send } = useMessages(counterpartUserKey, { refreshInterval: 4000 }); await send('hey there'); messages.map((m) => ( <Bubble key={m.message_id} mine={m.from === myKey}>{m.body}</Bubble> ));
Manage published sites — list, create, stage files, publish to CDN, and delete.
const { sites, isLoading, error, createSite, addFile, publishSite, deleteSite, refreshSites } = useSites()
const { sites, // PublishedSiteInfo[] isLoading, // boolean error, // string | null createSite, // (params: CreateSiteParams) => Promise<PublishedSiteInfo | null> addFile, // (siteId, path, content) => Promise<boolean> publishSite, // (siteId) => Promise<PublishedSiteInfo | null> deleteSite, // (siteId) => Promise<boolean> refreshSites, // () => Promise<void> } = useSites();
// Quick publish — pass files inline await createSite({ name: 'My Dashboard', siteType: 'on_demand', files: { 'index.html': ' ... ', 'styles.css': 'body { ... }' }, }); // Staged publish — add files incrementally, then publish const site = await createSite({ name: 'My App' }); await addFile(site.id, 'index.html', ' ... '); await addFile(site.id, 'src/App.tsx', 'export default function App() { ... }'); await publishSite(site.id);
List a built-app user's private message threads, with unread counts and consent control.
hook, built-apps, Flowstack SDK, useMessages, listThreads, openThread, DmThread
const { threads, isLoading, error, refresh, openThread } = useThreads(options?)
const { threads, isLoading, openThread } = useThreads({ refreshInterval: 5000 }); threads.map((t) => ( <ThreadRow key={t.pair_key} counterpart={t.with_user_key} unread={t.unread_count} /> )); // Consent to open a new conversation const status = await openThread(otherUserKey); // 'pending' | 'open' | null
Client function: activate a domain once its ACM cert is issued (CloudFront goes live ~5–15 min after).
utility, built-apps, Flowstack SDK, getDomainStatus, connectDomain
activateDomain(credentials, siteId, domain, config?): Promise<ApiResponse<ActivateDomainResult>>
const res = await activateDomain(credentials, siteId, 'app.example.com', { appScope }); if (res.ok) console.log('live at', res.data.url);
Client function: clear all staged files for a site (the .staging marker is preserved).
utility, built-apps, Flowstack SDK, stageFiles, listStagedFiles
clearStagedFiles(credentials, siteId, config?): Promise<ApiResponse<{ cleared: boolean; site_id: string; deleted_count: number }>>
await clearStagedFiles(credentials, siteId, { appScope });
Client function: start connecting a custom domain — requests an ACM cert and returns the DNS records to add.
utility, built-apps, Flowstack SDK, getDomainStatus, activateDomain, listDomains, disconnectDomain, CustomDomainRecord
connectDomain(credentials, siteId, domain, config?): Promise<ApiResponse<ConnectDomainResult>>
const res = await connectDomain(credentials, siteId, 'app.example.com', { appScope }); if (res.ok) { for (const r of res.data.validation_records) console.log(`${r.type} ${r.name} -> ${r.value}`); console.log('routing:', res.data.routing_record); }
Client function: disconnect a custom domain from a site.
utility, built-apps, Flowstack SDK, connectDomain, listDomains
disconnectDomain(credentials, siteId, domain, config?): Promise<ApiResponse<{ success: boolean; domain: string; site_id: string; message: string }>>
await disconnectDomain(credentials, siteId, 'app.example.com', { appScope });
Pure helper: deterministic, order-independent thread key for a pair of user keys.
utility, built-apps, Flowstack SDK, openThread, useThreads, DmThread
dmPairKey('userA', 'userB'); // 'userA::userB' dmPairKey('userB', 'userA'); // 'userA::userB' (same)
Client function: fetch a version's source files as a { path: content } map (live version by default).
utility, built-apps, Flowstack SDK, stageFiles, useSiteVersions
getAppSource(credentials, siteId, version?, config?): Promise<ApiResponse<AppSource>>
const res = await getAppSource(credentials, siteId, undefined, { appScope }); if (res.ok) for (const [p, content] of Object.entries(res.data.files)) console.log(p, content.length);
Client function: get the saved mongoCollections schema (data plan) for a site.
utility, built-apps, Flowstack SDK, setDataPlan
getDataPlan(credentials, siteId, config?): Promise<ApiResponse<DataPlan>>
const res = await getDataPlan(credentials, siteId, { appScope });
Client function: poll a custom domain's validation/activation status.
utility, built-apps, Flowstack SDK, connectDomain, activateDomain, CustomDomainRecord
getDomainStatus(credentials, siteId, domain, config?): Promise<ApiResponse<DomainStatusResult>>
const res = await getDomainStatus(credentials, siteId, 'app.example.com', { appScope }); if (res.ok && res.data.status === 'issued') { await activateDomain(credentials, siteId, 'app.example.com', { appScope }); }
Client function: list the custom domains connected to a site and their status.
utility, built-apps, Flowstack SDK, connectDomain, getDomainStatus, CustomDomainRecord
listDomains(credentials, siteId, config?): Promise<ApiResponse<{ site_id: string; domains: CustomDomainRecord[] }>>
const res = await listDomains(credentials, siteId, { appScope }); if (res.ok) res.data.domains.forEach(d => console.log(d.domain, d.status));
Client function: list messages in the caller's thread with a counterpart. ACL'd server-side.
utility, built-apps, Flowstack SDK, useMessages, sendMessage, DmMessage
listMessages(credentials, withUserKey, options?, config?): Promise<ApiResponse<{ messages: DmMessage[]; count: number }>>
const res = await listMessages(credentials, counterpartKey, { limit: 50 }, { appScope }); if (res.ok) console.log(res.data.messages);
Client function: list the files currently staged for a site.
utility, built-apps, Flowstack SDK, stageFiles, clearStagedFiles
listStagedFiles(credentials, siteId, config?): Promise<ApiResponse<StagedFilesList>>
const res = await listStagedFiles(credentials, siteId, { appScope }); if (res.ok) console.log(res.data.count, 'staged');
Client function: list the caller's DM threads (counterpart key, last message, unread count).
utility, built-apps, Flowstack SDK, useThreads, listMessages, DmThread
listThreads(credentials, config?): Promise<ApiResponse<{ threads: DmThread[]; count: number }>>
const res = await listThreads(credentials, { appScope }); if (res.ok) console.log(res.data.threads);
Client function: mark a received DM as read (recipient only).
utility, built-apps, Flowstack SDK, useMessages, listMessages, DmMessage
markMessageRead(credentials, messageId, config?): Promise<ApiResponse<{ message_id: string; read: boolean }>>
await markMessageRead(credentials, msg.message_id, { appScope });
Client function: record the caller's consent to open a DM thread with another user.
utility, built-apps, Flowstack SDK, useThreads, sendMessage, dmPairKey
openThread(credentials, withUserKey, config?): Promise<ApiResponse<{ pair_key: string; status: string }>>
const res = await openThread(credentials, otherUserKey, { appScope }); if (res.ok) console.log(res.data.status); // 'pending' until the other party also consents
Client function: buy a domain through Casino — returns a Stripe Checkout URL; registration + site auto-connect fire from the payment webhook (P0-163).
utility, built-apps, Flowstack SDK, searchDomains, PurchaseDomainRequest, RegistrantContact, listDomains, getDomainStatus
purchaseDomain(credentials, request, config?): Promise<ApiResponse<{ domain: string; price_usd: number; session_url: string; session_id: string }>>
Buy a domain and auto-connect it to a site
const res = await purchaseDomain(credentials, { domain: 'thezengarden', tld: 'com', registrant: { first_name: 'Ada', last_name: 'Lovelace', email: 'ada@example.com', phone: '+12125551234', address_line1: '1 Main St', city: 'New York', state: 'NY', zip_code: '10001', country_code: 'US', }, auto_renew: true, whois_privacy: true, site_id: siteId, }); if (res.data?.session_url) window.location.href = res.data.session_url;
Client function: rename a site (display name in the manifest + dashboard metadata).
utility, built-apps, Flowstack SDK, getAppSource, useSiteVersions
renameSite(credentials, siteId, name, config?): Promise<ApiResponse<{ site_id: string; name: string }>>
const res = await renameSite(credentials, siteId, 'My CRM', { appScope }); if (!res.ok) console.error(res.error);
Client function: check domain availability + Casino retail pricing across TLDs (P0-163).
utility, built-apps, Flowstack SDK, purchaseDomain, DomainSearchResult, connectDomain, listDomains
searchDomains(credentials, name, tlds?, config?): Promise<ApiResponse<{ results: DomainSearchResult[]; degraded?: boolean }>>
const res = await searchDomains(credentials, 'thezengarden', 'com,dev,app'); if (res.data?.degraded) { showError('Domain search is temporarily unavailable — try again shortly.'); } else { for (const r of res.data.results) { if (r.available) console.log(`${r.domain} — $${r.price_usd}/yr`); } }
Client function: send a private message. 403 unless the thread is mutually open.
utility, built-apps, Flowstack SDK, useMessages, openThread, listMessages
sendMessage(credentials, toUserKey, body, config?): Promise<ApiResponse<{ message_id: string; created_at: string }>>
const res = await sendMessage(credentials, toUserKey, 'hey there', { appScope }); if (!res.ok) console.error(res.error); // e.g. thread not open
Client function: merge collection schemas into the site's data plan (PUT-with-merge).
utility, built-apps, Flowstack SDK, getDataPlan
setDataPlan(credentials, siteId, collections, config?): Promise<ApiResponse<{ site_id: string; saved: string[]; mongoCollections: Record<string, unknown> }>>
await setDataPlan(credentials, siteId, { tasks: { fields: { title: 'string', done: 'boolean' } }, }, { appScope });
Client function: stage multiple files in one call (batch form of addSiteFile).
utility, built-apps, Flowstack SDK, listStagedFiles, clearStagedFiles, getAppSource
stageFiles(credentials, siteId, files, config?): Promise<ApiResponse<{ staged: boolean; site_id: string; staged_count: number; paths: string[]; total_bytes: number }>>
const res = await stageFiles(credentials, siteId, { 'index.html': ' hi ', 'app.js': 'console.log(1)', }, { appScope });
Type: a connected custom domain and its lifecycle status.
type, built-apps, Flowstack SDK, connectDomain, listDomains, getDomainStatus
interface CustomDomainRecord { domain: string; site_id: string; status: 'validating' | 'issued' | 'activating' | 'live' | 'failed'; cert_arn?: string; connected_at?: string; activated_at?: string; last_check?: string; cert_status?: string }
A single private message within a DM thread.
type, built-apps, Flowstack SDK, useMessages, listMessages, sendMessage, DmThread
interface DmMessage { message_id: string; from: string; to: string; body: string; created_at: string; read_at: string | null }
A private message thread between the caller and one counterpart.
type, built-apps, Flowstack SDK, useThreads, listThreads, DmMessage, dmPairKey
interface DmThread { pair_key: string; with_user_key: string; status: 'pending' | 'open'; last_message: DmMessage | null; unread_count: number; updated_at: string | null }
One TLD's availability + Casino retail price from searchDomains (P0-163).
type, built-apps, Flowstack SDK, searchDomains, purchaseDomain, PurchaseDomainRequest
Request body for purchaseDomain: name + TLD + registrant contact + auto-renew/privacy/auto-connect options (P0-163).
type, built-apps, Flowstack SDK, purchaseDomain, RegistrantContact, DomainSearchResult, searchDomains
WHOIS registrant contact for a domain purchase — registrar-validated formats (P0-163).
type, built-apps, Flowstack SDK, PurchaseDomainRequest, purchaseDomain, searchDomains
Read and mutate Casino-platform auth state (login, register, Google sign-in, logout, token refresh).
hook, auth, Flowstack SDK, BrokeredLoginButton, useFlowstackStatus, AuthGuard
const { user, credentials, isAuthenticated, isLoading, error, login, register, googleSignIn, logout, refreshToken } = useAuth()
const { isAuthenticated, isInitialized, credentials } = useFlowstack();
Programmatic route protection that reports whether access is allowed and where to redirect.
hook, auth, Flowstack SDK, useAuth, AuthGuard
const { isAllowed, isLoading, shouldRedirect, redirectTo } = useAuthGuard(options)
const { isAllowed, // boolean isLoading, // boolean shouldRedirect, // boolean redirectTo, // string | undefined } = useAuthGuard({ requireAuth: true, requireWorkspace: true, redirectTo: '/login', });
Wrapper that renders children only when authenticated — showing a login fallback, or transparently issuing a guest session for opted-in built apps.
component, auth, Flowstack SDK, BrokeredLoginButton, useFlowstack, useAuthGuard, useAuth
<AuthGuard fallback={<LoginScreen />}>{children}</AuthGuard>
import { AuthGuard } from 'flowstack-sdk'; export default function App() { return ( <AuthGuard fallback={<LoginScreen />}> <Console /> </AuthGuard> ); } // LoginScreen shows BrokeredLoginButton function LoginScreen() { return ( <BrokeredLoginButton /> ); }
The only authentication method for built apps — a button that opens Casino's auth popup where Privy runs and posts a scoped JWT back to the app.
component, auth, Flowstack SDK, useFlowstack, AuthGuard, useAuth, FlowstackProvider
import { useFlowstack, BrokeredLoginButton } from 'flowstack-sdk'; function LoginPage() { // No email, no password, no Privy appId needed here return ( Welcome <BrokeredLoginButton label="Continue with Flowstack" /> ); }
Agent-Driven Data Models — Reason → Write → Read
The core pattern for using an agent as a worker: the app reads context and passes it in the prompt, the agent reasons and writes a structured document, and the app reads the result back via useCollection.
guide, data, Flowstack SDK, useAgent, useCollection, two-layer-data-model
1. Read context in the app, PII-safe projection
// Read the shared pool the agent will reason over. const pool = useCollection<Record>('records', { layer: 'shared', limit: 200 }); // Read the current user's OWN row with a dedicated 1-doc query — do NOT find() it // inside the pool. The pool can settle a beat later, so `pool.documents.find(...)` // reads as "missing" on first paint even when the row exists (a real, hard-won bug). // A filtered limit:1 query resolves it independently of the pool load. const mine = useCollection<Record>('records', { layer: 'shared', filter: { owner_key: myKey }, limit: 1, refreshOnAgentComplete: true, }); const self = mine.documents[0] ?? null; const others = pool.documents.filter(r => r.owner_key !== myKey); // PII-safe projection before the agent ever sees a row. const toSafe = (r: Record) => ({ owner_key: r.owner_key, title: r.title, tags: r.tags, // NO email, NO phone });
2. Deterministic prompt naming the exact document shape
function buildPrompt(self, others, nowIso: string) { return [ `TASK — produce a result for owner_key "${self.owner_key}".`, 'Use the data provided below; do NOT query the database — it is all here.', '', 'SUBJECT:', JSON.stringify(self, null, 2), `CONTEXT (${others.length}):`, JSON.stringify(others, null, 2), '', 'Steps:', '1. Reason over the inputs.', '2. WRITE exactly one document to the "results" collection with insert_documents.', ` Use this exact run_at (do NOT call a time tool): "${nowIso}". The document MUST be:`, ' { "owner_key": "...", "run_at": "...", "status": "fresh",', ' "items": [{ "ref", "score", "reasons": [] }] }', '3. ALWAYS write the doc (even if items is empty). Never include email/phone.', 'Then reply with one short sentence summarizing the result.', ].join('\n'); }
3. One-shot query, read result back reactively
const agent = useAgent('custom', { capabilities: ['data_access'], sessionKey: 'worker' }); // The result collection — reads back what the agent writes. const results = useCollection<ResultDoc>('results', { layer: 'user', // per-user results; no owner_key filter needed (partition is scoped) sort: { run_at: -1 }, limit: 5, refreshOnAgentComplete: true, // auto-refresh when the agent's insert_documents completes }); async function run() { await Promise.all([pool.refresh(), mine.refresh()]); if (!self) return; // the 1-doc query may settle after first paint — gate on it await agent.query(buildPrompt(toSafe(self), others.map(toSafe), new Date().toISOString())); // results.documents[0] now holds the agent-written result (auto-refreshed). Render it directly. } const latest = results.documents[0]; const items = latest?.items ?? [];
4. Upsert from the agent — ensure the row exists before you update it
// When an agent ENRICHES a record over a conversation, update_documents alone // silently no-ops if the row doesn't exist yet — the user 'finishes' the chat but // has no saved record (a real, hard-won bug). Tell the agent to UPSERT: insert a // starter row first, then update it as it learns. function enrichPrompt(ownerKey: string) { return [ `ENRICH MODE. owner_key: "${ownerKey}".`, 'FIRST, query the "profiles" collection for this owner_key.', 'If there is NO doc, INSERT one now (insert_documents) as a starter row:', ` { "owner_key": "${ownerKey}", "fields": {}, "created_at": <ISO> }`, 'THEN, as you learn facts, UPDATE that doc (update_documents, filter by', 'owner_key) and set updated_at. Keep nuanced observations as append-only notes.', ].join('\n'); } // The app reflects the row passively — it never 'completes' on a chat marker. // With refreshOnAgentComplete, the indicator flips once the row lands. const mineP = useCollection<Profile>('profiles', { layer: 'shared', filter: { owner_key: myKey }, limit: 1, refreshOnAgentComplete: true, }); const ready = !!mineP.documents[0];
Two-layer data model — shared vs per-user
How useCollection and the agent's data tools resolve a collection to one of two namespaces — a physically isolated per-user database or a single world-readable shared pool — via the layer option.
guide, data, Flowstack SDK, useCollection, reason-write-read, useAgent
File upload to MongoDB via useCollection
const { insert } = useCollection('transactions'); const handleCSVUpload = async (file: File) => { const text = await file.text(); const rows = Papa.parse(text, { header: true }).data; await insert(rows.map(row => ({ ...row, amount: Number(row.amount), imported_at: new Date().toISOString(), }))); // All useCollection('transactions') instances auto-refresh };
Save form submissions and simple CRUD deterministically — no LLM, no tokens, no streaming
recipe, data, Flowstack SDK, per-user-store, shared-pool, reason-write-read, useToolInvocation, useCollection
import { useState } from 'react'; import { useCollection } from '@flowstack/sdk'; export function ContactForm() { const [email, setEmail] = useState(''); const [msg, setMsg] = useState(''); const { insert } = useCollection('submissions', { layer: 'shared' }); return ( { e.preventDefault(); await insert({ email, msg, submitted_at: new Date().toISOString() }); setEmail(''); setMsg(''); }} > setEmail(e.target.value)} placeholder="Email" /> setMsg(e.target.value)} /> Send ); }
Import a workspace library item (dataset, report, document, visualization) into the app's agent and analyze it
recipe, data, Flowstack SDK, reason-write-read, casino_list_library, casino_import_from_library, casino_talk_to_agent, useCollection
// Build-time (MCP path — Claude driving the casino tools): // 1. casino_list_library({ item_type: "dataset" }) // 2. casino_import_from_library({ site_id, item_id }) // 3. casino_talk_to_agent({ // site_id, // agent_name: "analyst", // message: "Analyze the imported sales dataset; insert one doc per finding into " + // "findings: {title, detail, metric, created_at (ISO)}.", // file_refs: ["sales_2026.csv"], // }) // // App-side read (tsx): // const { documents } = useCollection('findings', { // sort: { created_at: -1 }, // refreshOnAgentComplete: true, // });
Give each signed-in user their own private data (notes, entries, history) isolated from every other user
recipe, data, Flowstack SDK, shared-pool, form-write-direct, reason-write-read, useCollection
import { useState } from 'react'; import { useCollection } from '@flowstack/sdk'; export function Notes() { const [draft, setDraft] = useState(''); const { documents: notes, insert, remove } = useCollection('notes', { layer: 'user', sort: { created_at: -1 }, }); return ( <> setDraft(e.target.value)} /> { await insert({ text: draft, created_at: new Date().toISOString() }); setDraft(''); }} > Add note {notes.map(n => ( {n.text} remove({ _id: n._id })}>× ))} </> ); }
Run an agent prompt on a cron schedule and surface its output in the app (daily digests, weekly reports)
recipe, data, Flowstack SDK, reason-write-read, useAutomations, useCollection, casino_create_automation
import { useAutomations, useCollection } from '@flowstack/sdk'; export function DailyDigest() { const { automations, create, pause, resume } = useAutomations(); const digest = automations.find(a => a.name === 'daily-digest'); const { documents: reports } = useCollection('daily_reports', { sort: { run_at: -1 }, limit: 7, }); return ( <> {!digest && ( create({ name: 'daily-digest', prompt: 'Summarize yesterday\'s rows in the transactions collection and insert ' + 'one doc into daily_reports: {summary, total, run_at (ISO)}.', schedule: '0 8 * * *', output_config: { type: 'silent' }, }) } > Enable daily digest )} {digest && ( (digest.status === 'paused' ? resume(digest.id) : pause(digest.id))}> {digest.status === 'paused' ? 'Resume' : 'Pause'} )} {reports.map(r => {r.summary} )} </> ); }
Share one pool of data across all users of the app (leaderboards, feeds, catalogs), readable even before sign-in
recipe, data, Flowstack SDK, per-user-store, form-write-direct, usePublicCollection, useCollection
import { useCollection, usePublicCollection } from '@flowstack/sdk'; export function Leaderboard({ signedIn }: { signedIn: boolean }) { // Public, pre-auth read: const { documents: publicRows } = usePublicCollection('scores', { sort: { points: -1 }, limit: 10, }); // Signed-in write path: const { insert } = useCollection('scores', { layer: 'shared' }); return ( <> {publicRows.map(r => {r.player}: {r.points} )} {signedIn && ( insert({ player: 'me', points: 1, at: new Date().toISOString() })}> Post score )} </> ); }
Direct MongoDB CRUD for built-app data — the primary, reactive data hook for tables, forms, cards, and persistence.
hook, data, Flowstack SDK, usePublicCollection, useAgent, useDatasets, useQuery, two-layer-data-model
const { documents, count, total, isLoading, error, refresh, insert, update, remove } = useCollection<T>(name, options?)
function TransactionTable() { const { documents, isLoading } = useCollection<Transaction>('transactions', { sort: { date: -1 }, limit: 50, refreshOnAgentComplete: true, }); if (isLoading) return Loading... ; return ( {documents.map(row => ( {row.date} ${row.amount} ))} ); }
const { insert } = useCollection('transactions'); await insert({ ...form, amount: Number(form.amount), created_at: new Date().toISOString() }); // All useCollection('transactions') instances auto-refresh
const { update, remove } = useCollection('transactions'); await update({ _id: docId }, { $set: { category: 'Food' } }); await remove({ _id: docId });
Browse, query, export, and delete a specific MongoDB collection.
hook, data, Flowstack SDK, useCollection, useUserCollections, useDataOverview
Get a unified summary of all user-owned data.
hook, data, Flowstack SDK, useUserCollections, useCollectionExplorer, useDatasets
List, upload, download, and delete datasets, with reactive loading and error state.
hook, data, Flowstack SDK, useCollection, useVisualizations, useReports, useDataSources, useQuery
const { datasets, isLoading, error, uploadDataset, downloadDataset, deleteDataset, refreshDatasets } = useDatasets()
const { datasets, // DatasetInfo[] — { id, name, rows, columns, schema?, columnNames?, createdAt?, updatedAt? } isLoading, // boolean error, // string | null uploadDataset, // (file, name?) => Promise<DatasetInfo | null> downloadDataset, // (name) => Promise<Blob | null> deleteDataset, // (name) => Promise<boolean> refreshDatasets, // () => Promise<void> } = useDatasets();
Manage external data source connections — create, test, delete, and list.
hook, data, Flowstack SDK, useDatasets, useQuery
const { dataSources, isLoading, error, createDataSource, testConnection, deleteDataSource, refreshDataSources } = useDataSources()
const { dataSources, // DataSource[] isLoading, // boolean error, // string | null createDataSource, // (config) => Promise<DataSource | null> testConnection, // (sourceId) => Promise<boolean> deleteDataSource, // (sourceId) => Promise<boolean> refreshDataSources,// () => Promise<void> } = useDataSources();
Anonymous, no-auth public collection reads and inserts for leaderboards, guestbooks, comment threads, and voting.
hook, data, Flowstack SDK, useCollection, useAgent
const { documents, count, total, isLoading, error, insert, refresh } = usePublicCollection<T>(name, options?)
import { usePublicCollection } from 'flowstack-sdk'; const { documents, // T[] count, // number total, // number isLoading, // boolean error, // string | null insert, // (doc: Partial<T>) => Promise<{ inserted_id: string }> refresh, // () => Promise<void> } = usePublicCollection<HighScore>('high_scores', { filter: { album: 'yeezus' }, sort: { score: -1 }, limit: 25, }); await insert({ album: 'yeezus', name: 'KEON', score: 12500 });
Builder config required in app_config.json
{ "publicCollections": { "high_scores": { "schema": { "album": { "type": "string", "required": true, "maxLength": 32 }, "name": { "type": "string", "required": true, "maxLength": 24 }, "score": { "type": "number", "required": true, "min": 0 } }, "rateLimit": { "writesPerMinute": 5, "writesPerDay": 50 } } } }
Run a single one-shot query from a natural-language prompt without streaming.
hook, data, Flowstack SDK, useCollection, useDatasets, useDataSources
const { executeQuery, result, isLoading, error } = useQuery()
const { executeQuery, // (prompt: string) => Promise<QueryResult> result, // QueryResult | null isLoading, // boolean error, // string | null } = useQuery();
List generated reports with reactive loading and error state.
hook, data, Flowstack SDK, useDatasets, useVisualizations, useQuery
const { reports, isLoading, error, refreshReports } = useReports()
const { reports, // ReportInfo[] — { name, content, format, createdAt } isLoading, // boolean error, // string | null refreshReports, // () => Promise<void> } = useReports();
List all MongoDB collections the user owns.
hook, data, Flowstack SDK, useCollection, useCollectionExplorer, useDataOverview
List generated visualizations with reactive loading and error state.
hook, data, Flowstack SDK, useDatasets, useReports, useQuery
const { visualizations, isLoading, error, refreshVisualizations } = useVisualizations()
const { visualizations, // VisualizationData[] — { name, type, imageUrl, imageBase64, format, createdAt } isLoading, // boolean error, // string | null refreshVisualizations,// () => Promise<void> } = useVisualizations();
There are no pre-built ecosystem agents — agents are per-app; you create them with casino_create_agent via the Casino MCP and target them by name.
guide, ai, Flowstack SDK, targeting-custom-agents, tool-categories, tool-access-control, useAgent, useAgents
Creating an agent (MCP) — category grant
casino_create_agent( site_id="<your_site_id>", name="support_bot", system_prompt="You are a helpful support assistant...", capabilities=["data_access"] // scopes what tools this agent can use )
Creating an agent (MCP) — per-tool grant
casino_create_agent( site_id="<your_site_id>", name="readonly_bot", system_prompt="You are a read-only support assistant...", tools=["query_mongodb", "count_documents"] // only these 2 tools, no writes )
const { query, messages } = useAgent(undefined, { persona: 'support_bot', capabilities: ['data_access'], });
Pre-configured agent templates (data-science, marketing, support) plus a dynamic Agent Factory that routes user intent to the best registered agent.
guide, ai, Flowstack SDK, useAgent, ChatInterface, agent-catalog
import { dataScienceTemplate, marketingTemplate, supportTemplate, createCustomTemplate } from 'flowstack-sdk'; // Use with ChatInterface <ChatInterface template="data-science" /> <ChatInterface template="marketing" /> <ChatInterface template="support" /> // Custom template const custom = createCustomTemplate({ streaming: true, networkMode: 'PUBLIC', systemPrompt: 'You are a helpful assistant for ...', });
import { AgentFactory, IntentAnalyzer, AgentRegistry, DEFAULT_PATTERNS } from 'flowstack-sdk'; // Register agents const registry = new AgentRegistry(); registry.register({ name: 'data-analyst', description: 'Analyzes datasets and creates visualizations', template: dataScienceTemplate, patterns: [/analyz/i, /chart/i, /data/i], }); // Analyze user intent and route to best agent const analyzer = new IntentAnalyzer({ registry }); const intent = analyzer.analyze('Show me a chart of Q4 revenue'); // → { category: 'data-analysis', confidence: 0.92, entities: ['Q4 revenue'] }
Built apps target an agent they create via `casino_create_agent` by passing its registered name as `persona` — a target is required for app-scoped users or they get a 403.
guide, ai, Flowstack SDK, useAgent, useAgents, agent-catalog, tool-categories
// 1. Create your agent via MCP first (once, at setup time): // casino_create_agent(site_id, "support_bot", "You are a helpful support assistant...", capabilities=["data_access"]) // OR for per-tool control: casino_create_agent(site_id, "support_bot", "...", tools=["query_mongodb", "count_documents"]) // 2. Target it from your built app: const { query, messages, isStreaming } = useAgent(undefined, { persona: 'support_bot', // must match the name you registered capabilities: ['data_access'], // advisory — the registered persona's grant is authoritative });
Pass an explicit tools list to casino_create_agent for per-tool access control, which overrides category-level capabilities grants.
guide, ai, Flowstack SDK, tool-categories, targeting-custom-agents, agent-catalog
Discover tools, then create a read-only bot
// Step 1 — discover available tool names: casino_list_tools() // → returns { "data": ["query_mongodb", "insert_documents", ...], "capabilities": [...], ... } // Step 2 — create a read-only support bot (no write access): casino_create_agent( site_id="<your_site_id>", name="support_bot", system_prompt="You are a helpful support assistant...", tools=["query_mongodb", "count_documents", "describe_collection"] ) // → capabilities auto-derived as ["data_access"], tools enforced at runtime
The nine capability categories you declare on a registered agent (and, advisorily, on `useAgent`), each granting a group of related tools.
guide, ai, Flowstack SDK, targeting-custom-agents, tool-access-control, agent-catalog
Run AI agent queries with streaming, tool calls, interrupts, status updates, and automatic conversation persistence.
hook, ai, Flowstack SDK, useCollection, useAuth, useDatasets
const { messages, isStreaming, isLoading, toolCalls, connectedDataSources, error, query, clearMessages, startNewSession, cancelQuery, interruptAgent, respondToInterrupt } = useAgent(template?, options?)
// Each surface gets its own brain + isolated conversation. const chat = useAgent('custom', { capabilities: ['data_access'], persona: 'assistant', sessionKey: 'chat' }); const worker = useAgent('custom', { capabilities: ['data_access'], persona: 'worker', sessionKey: 'tasks' });
function AssistantChat() { const { query, messages, isStreaming } = useAgent(undefined, { persona: 'assistant' // agent registered via casino_create_agent }); // Turn 1: "summarize this dataset" // Turn 2: "now group it by region" ← agent remembers "this dataset" // Turn 3: "which region is the biggest?" ← agent knows the grouping from turn 2 return ( <ChatUI messages={messages} onSend={query} isStreaming={isStreaming} /> ); }
Persist a conversation across reloads (0.2.1+)
const chat = useAgent('custom', { capabilities: ['data_access'], sessionKey: 'support-chat' }); // Reopening/reloading this surface resumes the same conversation; a different sessionKey is a // different conversation. Call chat.clearMessages() / startNewSession() to start fresh.
Discover the agents available to the current site, including their name, description, tools, trigger phrases, and intended use.
hook, ai, Flowstack SDK, useAgent, targeting-custom-agents, agent-catalog
const { agents, isLoading, refreshAgents } = useAgents()
const { agents, isLoading, refreshAgents } = useAgents(); // agents: AgentInfo[] — each has name, description, tools, triggerPhrases, useFor
Fetch the user's past Casino builder conversations.
hook, ai, Flowstack SDK, useAgent, useDataOverview
Create and manage agents dynamically based on user intent.
hook, ai, Flowstack SDK, useAgent, useAgents
ML model management — list registered models with their framework, metrics, and creation metadata.
const { models, isLoading, error, refreshModels } = useModels()
const { models, // ModelInfo[] — { name, framework, metrics, createdAt } isLoading, // boolean error, // string | null refreshModels, // () => Promise<void> } = useModels();
Detect a local Ollama instance and list its available models.
hook, ai, Flowstack SDK, useProviderCredentials, useModels
Manage LLM provider credentials (BYOK and Ollama).
hook, ai, Flowstack SDK, useOllamaDetection, useModels
Low-level hook for directly invoking an agent tool.
hook, ai, Flowstack SDK, useCollection, useAgent
Work with the version history of a published site.
hook, workspaces, Flowstack SDK, useSites
Admin hook to list, search, paginate, and mutate managed users plus view user stats and activity.
hook, workspaces, Flowstack SDK, useWorkspace
const { users, stats, isLoading, error, canManageUsers, pagination, refreshUsers, getUser, updateUser, suspendUser, reactivateUser, deleteUser, getUserActivity, refreshStats, setPage, setSearch, setRoleFilter, setStatusFilter } = useUserManagement()
const { users, // ManagedUser[] stats, // UserStats | null — { totalUsers, activeUsers, newUsersThisMonth } isLoading, // boolean error, // string | null canManageUsers, // boolean pagination, // { page, limit, totalCount, hasMore } refreshUsers, // (params?) => Promise<void> getUser, // (userId) => Promise<ManagedUser | null> updateUser, // (userId, updates) => Promise<boolean> suspendUser, // (userId, reason?) => Promise<boolean> reactivateUser, // (userId) => Promise<boolean> deleteUser, // (userId) => Promise<boolean> getUserActivity, // (userId, limit?) => Promise<UserActivityLog[]> refreshStats, // () => Promise<void> setPage, // (page) => void setSearch, // (search) => void setRoleFilter, // (role) => void setStatusFilter, // (status) => void } = useUserManagement();
List, create, and select workspaces for the current user.
hook, workspaces, Flowstack SDK, useUserManagement
const { workspaces, selectedWorkspace, isLoading, error, createWorkspace, selectWorkspace, refreshWorkspaces } = useWorkspace()
const { workspaces, // WorkspaceInfo[] selectedWorkspace, // WorkspaceInfo | null isLoading, // boolean error, // string | null createWorkspace, // (name, description?) => Promise<WorkspaceInfo | null> selectWorkspace, // (workspace) => void refreshWorkspaces, // () => Promise<void> } = useWorkspace();
Complete pre-built auth page with login/register tabs and optional Google sign-in.
component, components, Flowstack SDK, LoginForm, RegisterForm, AuthGuard
Complete auth page with login/register tabs
<AuthPage defaultTab="login" onSuccess={() => router.push('/dashboard')} showGoogle />
Embeddable chat interface bound to an agent template.
component, components, Flowstack SDK, ChatPage, MessageList, MarkdownRenderer, useAgent
<ChatInterface template="data-science" height={500} placeholder="Ask about your data..." />
component, components, Flowstack SDK, ChatInterface, MessageList, MarkdownRenderer
<ChatPage title="AI Assistant" placeholder="Ask anything..." welcomeMessage={<Welcome />} />
Modal dialog for creating a new workspace.
component, components, Flowstack SDK, WorkspaceSelector
<CreateWorkspaceModal isOpen={open} onCreated={handleCreated} />
Pre-built dashboard layout with a sidebar, header, and content area.
component, components, Flowstack SDK, AuthPage, ChatPage
<DashboardLayout sidebar={<Sidebar />} header={ }> <Content /> </DashboardLayout>
File-upload component for ingesting datasets such as CSV, XLSX, and Parquet files.
component, components, Flowstack SDK, useDatasets
<DatasetUploader onUpload={handleUpload} accept=".csv,.xlsx,.parquet" />
Pre-built login form with success/error callbacks and an optional register link.
component, components, Flowstack SDK, RegisterForm, AuthGuard, AuthPage
<LoginForm onSuccess={handleSuccess} onError={handleError} showRegisterLink />
Drop-in component for rendering agent responses with full GFM support and automatic mid-stream table repair.
component, components, Flowstack SDK, useAgent, MessageList, ChatInterface
import { useAgent, MarkdownRenderer } from 'flowstack-sdk'; function Chat() { const { messages, isStreaming } = useAgent('data-science'); return ( {messages.map(msg => msg.role === 'assistant' ? ( <MarkdownRenderer key={msg.id} content={msg.content} isStreaming={isStreaming && msg === messages.at(-1)} /> ) : ( {msg.content} ) )} ); }
Renders a list of chat messages, with optional streaming state.
component, components, Flowstack SDK, ChatInterface, MarkdownRenderer, useAgent
<MessageList messages={messages} isStreaming={isStreaming} />
Pre-built registration form with a success callback and an optional login link.
component, components, Flowstack SDK, LoginForm, AuthGuard, AuthPage
<RegisterForm onSuccess={handleSuccess} showLoginLink />
Pre-built control for selecting the active workspace.
component, components, Flowstack SDK, CreateWorkspaceModal
<WorkspaceSelector onSelect={handleSelect} />
Separate `flowstack-sdk/wallet` entry point for the two token systems (AGENT build credits, INFER query credits): reading balances, accepting payments, and accessing wallet addresses.
guide, wallet, Flowstack SDK, useWalletAuth, useInferBalance, useAgentBalance, useDeposit, useBuyInfer, BrokeredLoginButton
Charge users of the app for access (unlock fee or metered queries) using the platform paywall
recipe, wallet, Flowstack SDK, call-stripe-api, casino_set_monetization, AppPaywall, useAppAccess
import { AppPaywall } from '@flowstack/sdk/paywall'; export default function App() { return ( <AppPaywall siteId={import.meta.env.VITE_APP_SCOPE}> <YourAppContent /> </AppPaywall> ); }
Poll the connected wallet's AGENT token balance so built apps can check whether the end-user can afford an operation.
hook, wallet, Flowstack SDK, useInferBalance, NeedsAgent, AgentBalanceBadge
const { data, isLoading, error, refetch } = useAgentBalance()
const { data } = useAgentBalance(); // data.available — spendable AGENT (balance minus active holds) // data.balance — total AGENT in wallet
Check whether the authenticated end-user has access to a monetized built app, and open Stripe Checkout to unlock it. Use directly only for custom gate UI — `AppPaywall` wraps this with a default overlay.
hook, wallet, Flowstack SDK, AppPaywall, setMonetization, getMonetization, PaymentRequired, AppSubscriptionStatus, subscribeToApp, cancelAppSubscription
const { hasAccess, queriesUsed, queriesRemaining, paymentMode, unlockPriceCents, unlockPriceLabel, isLoading, error, checkout, refetch } = useAppAccess(siteId, opts?)
import { useAppAccess } from 'flowstack-sdk/paywall'; function Gate({ children }: { children: React.ReactNode }) { const { hasAccess, queriesRemaining, unlockPriceLabel, isLoading, checkout } = useAppAccess(config.appScope); if (isLoading) return Checking access… ; if (hasAccess) return <>{children}</>; return ( {queriesRemaining === 0 ? 'Free queries used up.' : 'This app is premium.'} checkout()}>Unlock for {unlockPriceLabel} ); }
Open the fiat on-ramp widget to buy INFER tokens and track purchase status.
hook, wallet, Flowstack SDK, useDeposit, useInferBalance, BuyInferModal
const { buy, isBuying, status, error } = useBuyInfer()
Deposit INFER tokens from the connected wallet and track the resulting transaction hash.
hook, wallet, Flowstack SDK, useInferBalance, useBuyInfer
const { deposit, isDepositing, txHash, error } = useDeposit()
Read the connected wallet's INFER token balance, including available balance and query credits.
hook, wallet, Flowstack SDK, useAgentBalance, useDeposit, useBuyInfer, InferBalanceBadge
const { data, isLoading, error, refetch } = useInferBalance()
Read wallet connection state and trigger Privy/SIWE login or logout from the `flowstack-sdk/wallet` entry point.
hook, wallet, Flowstack SDK, useInferBalance, useAgentBalance, LoginButton, BrokeredLoginButton
const { isConnected, isLoading, address, isEmbeddedWallet, authMethod, error, login, logout } = useWalletAuth()
Display badge showing the connected wallet's AGENT token balance.
component, wallet, Flowstack SDK, useAgentBalance, InferBalanceBadge
Access gate for monetized built apps: renders children when the end-user has access, otherwise shows a paywall overlay with the unlock CTA. The default way to charge users of an app.
component, wallet, Flowstack SDK, useAppAccess, setMonetization, getMonetization, PaymentRequired, AppSubscriptionStatus
<AppPaywall siteId={config.appScope} builderTenantId?={...} onBlocked?={(reason) => ...}>{children}</AppPaywall>
import { AppPaywall } from 'flowstack-sdk/paywall'; export default function App() { return ( <AppPaywall siteId={config.appScope}> <YourAppContent /> </AppPaywall> ); }
component, wallet, Flowstack SDK, useBuyInfer, PaymentRequired, NeedsAgent
Display badge showing the connected wallet's INFER token balance.
component, wallet, Flowstack SDK, useInferBalance, AgentBalanceBadge
Privy/SIWE login button for the Casino platform only — built apps use `BrokeredLoginButton` instead.
component, wallet, Flowstack SDK, useWalletAuth, BrokeredLoginButton
Gate a feature behind a minimum AGENT balance, showing a deep-linked "Get AGENT →" CTA when the user has insufficient funds.
component, wallet, Flowstack SDK, useAgentBalance, PaymentRequired, BuyInferModal
<NeedsAgent amountNeeded={2} onProceed={fn} returnUrl={url}>{children}</NeedsAgent>
<NeedsAgent amountNeeded={2} onProceed={generateAlbumArt} returnUrl={window.location.href} > Generate a new design — costs 2 AGENT </NeedsAgent>
Gate content behind payment, rendering children only once the requirement is met.
component, wallet, Flowstack SDK, NeedsAgent, BuyInferModal
<PaymentRequired> <PaidContent /> </PaymentRequired>
Client function: cancel an app subscription at period end (P0-162).
utility, wallet, Flowstack SDK, subscribeToApp, useAppAccess, AppSubscriptionStatus
cancelAppSubscription(credentials, siteId, config?): Promise<ApiResponse<{ status: string; cancel_at_period_end: boolean; current_period_end: string | null }>>
await cancelAppSubscription(credentials, siteId, { appScope: siteId });
Client function: aggregate + per-app builder earnings across both rails (Stripe cents + on-chain AGENT).
utility, wallet, Flowstack SDK, requestBuilderPayout, getConnectStatus, BuilderEarnings, getMonetization
getBuilderEarnings(credentials, config?): Promise<ApiResponse<BuilderEarnings>>
const res = await getBuilderEarnings(credentials); if (res.ok) { const { available_cents, rails } = res.data; console.log(`Fiat available: $${(available_cents / 100).toFixed(2)}`); console.log(`AGENT accrued: ${rails.agent.accrued_agent}`); // human string — NOT wei }
Client function: live Stripe Connect account status for the builder (refreshes cached flags).
utility, wallet, Flowstack SDK, startConnectOnboarding, requestBuilderPayout, ConnectStatus
getConnectStatus(credentials, config?): Promise<ApiResponse<ConnectStatus>>
const res = await getConnectStatus(credentials); const canPayout = res.ok && res.data.payouts_enabled;
Client function: read a site's monetization (paywall) configuration.
utility, wallet, Flowstack SDK, setMonetization, Monetization
getMonetization(credentials, siteId, config?): Promise<ApiResponse<{ site_id: string; monetization: Monetization }>>
const res = await getMonetization(credentials, siteId, { appScope }); if (res.ok) console.log(res.data.monetization.enabled);
Client function: withdraw the builder's available fiat balance to their connected Stripe account.
utility, wallet, Flowstack SDK, getBuilderEarnings, getConnectStatus, startConnectOnboarding
requestBuilderPayout(credentials, config?): Promise<ApiResponse<{ paid_cents: number; stripe_transfer_id: string; payout_id: string }>>
const res = await requestBuilderPayout(credentials); if (res.ok) console.log(`Paid $${(res.data.paid_cents / 100).toFixed(2)}`); else console.error(res.error); // e.g. below minimum, payouts not enabled
Client function: set a site's monetization (paywall) configuration.
utility, wallet, Flowstack SDK, getMonetization, Monetization, getBuilderEarnings, getConnectStatus, MonetizationSubscriptionConfig
setMonetization(credentials, siteId, monetization, config?): Promise<ApiResponse<{ site_id: string; monetization: Monetization; warnings?: string[] }>>
await setMonetization(credentials, siteId, { enabled: true, payment_mode: 'agent', }, { appScope });
Client function: create (or reuse) the builder's Stripe Connect Express account and get an onboarding link.
utility, wallet, Flowstack SDK, getConnectStatus, requestBuilderPayout
startConnectOnboarding(credentials, urls, config?): Promise<ApiResponse<{ url: string; stripe_account_id: string }>>
const res = await startConnectOnboarding(credentials, { return_url: `${location.origin}/earnings?onboarded=1`, refresh_url: `${location.origin}/earnings?retry=1`, }); if (res.ok) window.location.href = res.data.url;
Client function: open Stripe Checkout for an app's monthly subscription plan (P0-162).
utility, wallet, Flowstack SDK, cancelAppSubscription, useAppAccess, AppSubscriptionStatus, MonetizationSubscriptionConfig
subscribeToApp(credentials, siteId, urls, config?): Promise<ApiResponse<{ url: string; site_id: string }>>
const res = await subscribeToApp(credentials, siteId, { success_url: window.location.href, cancel_url: window.location.href, }, { appScope: siteId }); if (res.data?.url) window.location.href = res.data.url;
The end-user's subscription state for one app: status and billing period — status-only since 0.3.3/P0-167 (P0-162).
type, wallet, Flowstack SDK, useAppAccess, AppPaywall, subscribeToApp, cancelAppSubscription, MonetizationSubscriptionConfig
Type: the builder's full earnings picture — both rails, self-query summary, per-app breakdown.
type, wallet, Flowstack SDK, getBuilderEarnings, requestBuilderPayout, Monetization
interface BuilderEarnings { earned_cents: number; paid_cents: number; available_cents: number; min_payout_cents: number; currency: string; builder_user_id: string; as_of: string; rails: { stripe: StripeRailEarnings; agent: AgentRailEarnings }; self_query: SelfQuerySummary; compute?: ComputeSpendSummary; apps: AppEarnings[] }
Type: the builder's Stripe Connect account state.
type, wallet, Flowstack SDK, getConnectStatus, startConnectOnboarding
interface ConnectStatus { connected: boolean; stripe_account_id?: string; payouts_enabled: boolean; details_submitted?: boolean }
Type: a built app's monetization (paywall) config — Stripe and/or AGENT-token payment.
type, wallet, Flowstack SDK, getMonetization, setMonetization, MonetizationSubscriptionConfig
interface Monetization { enabled?: boolean; payment_mode?: 'stripe' | 'agent' | 'both'; stripe?: MonetizationStripeConfig; agent?: MonetizationAgentConfig; revenue_share_bps?: number }
Monthly subscription plan config: $X/month for full access on the builder's Stripe Connect rails — pure access revenue since 0.3.3/P0-167 (P0-162).
type, wallet, Flowstack SDK, Monetization, setMonetization, AppSubscriptionStatus, subscribeToApp, getConnectStatus
interface MonetizationSubscriptionConfig
Use a registered HTTP API integration (notion, slack, linear, or owner-registered) from a built app via the agent
recipe, integrations, Flowstack SDK, call-stripe-api, register-custom-api, connect-oauth-service, useAgent, useCollection
import { useAgent, useCollection } from '@flowstack/sdk'; export function IssueBoard() { const { query, isStreaming } = useAgent(undefined, { capabilities: ['external_integration', 'data_access'], }); const { documents: issues } = useCollection('linear_issues', { layer: 'shared', sort: { updated_at: -1 }, refreshOnAgentComplete: true, }); return ( <> query( 'Call the linear integration: fetch open issues and upsert them into ' + 'linear_issues (dedupe on issue_id), matching its doc schema.' ) } > Sync issues {issues.map(i => {i.identifier}: {i.title} )} </> ); }
Operate on the OWNER's Stripe account (revenue dashboards, customer lookups, invoice ops) from a built app
recipe, integrations, Flowstack SDK, paywall-app, call-http-integration, register-custom-api, useAgent, useCollection
import { useAgent, useCollection } from '@flowstack/sdk'; export function RevenuePanel() { const { query, isStreaming } = useAgent(undefined, { capabilities: ['external_integration', 'data_access'], }); const { documents: events } = useCollection('payment_events', { layer: 'shared', sort: { created_at: -1 }, limit: 100, refreshOnAgentComplete: true, }); const totalCents = events.reduce((s, e) => s + (e.amount_cents ?? 0), 0); return ( <> query( 'Call the stripe integration: list the latest charges and upsert them into ' + 'payment_events (dedupe on event_id), matching its doc schema. No card data.' ) } > Refresh revenue ${(totalCents / 100).toFixed(2)} </> ); }
Let each user connect Google and use the sub-services their scopes actually grant (analytics, ads, drive, youtube)
recipe, integrations, Flowstack SDK, connect-strava, connect-oauth-service, useConnections, useAgent, useCollection
import { useConnections, useAgent, useCollection } from '@flowstack/sdk'; export function DrivePanel() { const { connections, connect } = useConnections(); const google = connections.find(c => c.provider === 'google'); const hasDrive = google?.connected && google.services?.includes('drive'); const { query } = useAgent(undefined, { capabilities: ['external_integration', 'data_access'], }); const { documents: files } = useCollection('drive_files', { layer: 'user', sort: { modified_at: -1 }, refreshOnAgentComplete: true, }); if (!hasDrive) { return connect('google', ['drive'])}>Connect Google Drive ; } return ( <> query('Sync my recent Drive files into drive_files (upsert on file_id).')}> Sync Drive {files.map(f => {f.name} )} </> ); }
Let each user connect an OAuth service (github, reddit, twitter, strava, google) and work with their data in the app
recipe, integrations, Flowstack SDK, connect-strava, connect-google-services, useConnections, useAgent, useCollection
import { useConnections, useAgent, useCollection } from '@flowstack/sdk'; export function GithubPanel() { const { connections, connect } = useConnections(); const github = connections.find(c => c.provider === 'github'); const { query } = useAgent(undefined, { capabilities: ['external_integration', 'data_access'], }); const { documents: repos } = useCollection('github_repos', { layer: 'user', sort: { updated_at: -1 }, refreshOnAgentComplete: true, }); if (!github?.connected) { return connect('github')}>Connect GitHub ; } return ( <> query('Sync my repos into github_repos (upsert on repo_id).')}> Sync repos {repos.map(r => {r.full_name} ★{r.stars} )} </> ); }
Let each user connect their Strava account and work with their activities in the app
recipe, integrations, Flowstack SDK, reason-write-read, connect-oauth-service, useConnections, useAgent, useCollection
import { useConnections, useAgent, useCollection } from '@flowstack/sdk'; export function StravaPanel() { const { connections, connect, isLoading } = useConnections(); const strava = connections.find(c => c.provider === 'strava'); const { query, isStreaming } = useAgent(undefined, { capabilities: ['external_integration', 'data_access'], }); const { documents: activities } = useCollection('strava_activities', { layer: 'user', sort: { start_date: -1 }, limit: 50, refreshOnAgentComplete: true, }); if (isLoading) return Loading… ; if (!strava?.connected) { return connect('strava')}>Connect Strava ; } return ( <> query( 'Fetch my 30 most recent Strava activities and upsert them into the ' + 'strava_activities collection (dedupe on activity_id), matching its doc schema.' ) } > Sync activities {activities.map(a => ( {a.name} — {(a.distance_m / 1000).toFixed(1)} km ))} </> ); }
Let the app OWNER register any HTTPS REST API as an integration the agent can call as a tool
recipe, integrations, Flowstack SDK, call-http-integration, call-stripe-api, connect-oauth-service, useIntegrations, useAgent
import { useIntegrations } from '@flowstack/sdk'; export function RegisterShopify() { const { integrations, create, isLoading } = useIntegrations(); if (integrations.some(i => i.name === 'shopify')) return Shopify registered. ; return ( create({ name: 'shopify', description: 'Shopify Admin API for order management and reporting', base_url: 'https://my-store.myshopify.com/admin/api/2024-01', auth_type: 'bearer', auth_config: { token: process.env.NEVER_HARDCODE_ME! }, endpoints: [ { name: 'list_orders', method: 'GET', path: '/orders.json' }, { name: 'get_order', method: 'GET', path: '/orders/{id}.json' }, ], }) } > Register Shopify ); }
Schedule agent prompts on a cron schedule (via AWS EventBridge) with silent, email, webhook, or file delivery.
hook, integrations, Flowstack SDK, useIntegrations, useConnections, useAgent
const { automations, isLoading, error, create, update, remove, pause, resume, runNow, getRuns, refresh } = useAutomations()
import { useAutomations } from 'flowstack-sdk'; const { automations, create, pause, resume, runNow, getRuns } = useAutomations(); // Daily sales digest — weekdays at 9 AM Eastern await create({ name: 'Daily sales digest', prompt: "Pull yesterday's orders from Shopify, summarize revenue by product and region, flag any anomalies.", schedule: '0 9 * * 1-5', timezone: 'America/New_York', target_agents: ['shopify_analyst'], // routes to a specific agent persona; omit for default output_config: { type: 'email', to: 'team@company.com', subject_template: 'Sales digest — {date}', }, }); // Run once right now (ignores schedule) await runNow(automationId); // Pause / resume await pause(automationId); await resume(automationId); // Get last 10 run results const runs = await getRuns(automationId, 10); // [{run_id, status, started_at, duration_ms, credits_used, output_summary, output_url}]
Manage a user's external service connections (Google, Reddit, Strava, Twitter/X) so the AI agent can access their data.
hook, integrations, Flowstack SDK, useIntegrations, useDataSources, useAgent
const { connections, connect, disconnect, isLoading } = useConnections()
import { useConnections } from 'flowstack-sdk'; const { connections, connect, disconnect, isLoading } = useConnections(); // Check status connections.google.connected // true | false connections.google.analytics // true | false (specific service) connections.google.email // "user@gmail.com" if connected connections.reddit.connected // true | false connections.strava.connected // true | false connections.twitter.connected // true | false // Connect — opens OAuth popup connect('google', ['analytics', 'drive']); // specific Google services connect('google', ['all']); // all Google services connect('reddit'); // Reddit connect('strava'); // Strava connect('twitter'); // Twitter/X // Disconnect disconnect('google'); disconnect('reddit');
function SettingsPage() { const { connections, connect, disconnect } = useConnections(); const services = [ { key: 'google', label: 'Google', sublabel: 'Analytics, Ads, Drive, YouTube', services: ['all'] as const }, { key: 'reddit', label: 'Reddit', sublabel: 'Feed access' }, { key: 'strava', label: 'Strava', sublabel: 'Activity data' }, { key: 'twitter', label: 'Twitter / X', sublabel: 'Timeline and bookmarks' }, ]; return ( Connected Services Connect your accounts so the AI assistant can access your data. {services.map(({ key, label, sublabel, services: svc }) => { const status = connections[key as keyof typeof connections]; return ( {label} {sublabel} {status.connected ? disconnect(key as any)}>Disconnect : connect(key as any, svc as any)}>Connect } ); })} ); }
Register any HTTPS REST API as a named integration the agent can call as a tool, with credentials encrypted at rest.
hook, integrations, Flowstack SDK, useAutomations, useConnections, useDataSources
const { integrations, isLoading, error, create, update, remove, get, refresh } = useIntegrations()
Register, update, and remove an integration
import { useIntegrations } from 'flowstack-sdk'; const { integrations, create, update, remove, isLoading } = useIntegrations(); // Register Shopify await create({ name: 'Shopify', description: 'Shopify Admin API for order and product management', base_url: 'https://my-store.myshopify.com/admin/api/2024-01', auth_type: 'bearer', // 'bearer' | 'api_key_header' | 'api_key_query' | 'basic' | 'none' auth_config: { token: 'shpat_xxx' }, endpoints: [ { name: 'list_orders', method: 'GET', path: '/orders.json' }, { name: 'get_order', method: 'GET', path: '/orders/{id}.json' }, { name: 'cancel_order',method: 'POST', path: '/orders/{id}/cancel.json' }, ], }); // Update credentials await update(id, { auth_config: { token: 'new_token' } }); // Remove await remove(id);
Monitor the backend connection status, latency, and connectivity with optional polling.
const { status, isConnected, latency, error, checkConnection } = useFlowstackStatus(options)
const { status, // 'connected' | 'disconnected' | 'checking' isConnected, // boolean latency, // number | null (ms) error, // string | null checkConnection, // () => Promise<void> } = useFlowstackStatus({ pollInterval: 30000, autoPoll: true, });
Lower-level promise-based API functions for direct backend access when hooks don't provide enough control.
utility, utilities, Flowstack SDK, flowstackFetch, useQuery, useWorkspace, useDatasets
fn(credentials, ...params, config?): Promise<ApiResponse<T>>
import { // Auth login, register, // Workspaces listWorkspaces, createWorkspace, getWorkspace, // Datasets listDatasets, getDataset, getDatasetPreview, deleteDataset, // Visualizations & Reports listVisualizations, listReports, // Models & Scripts listModels, getModel, listScripts, // Data Sources listDataSources, createDataSource, testDataSource, deleteDataSource, // Sites listSites, getSite, createSite, addSiteFile, publishStagedSite, deleteSite, // User Management listUsers, getUser, updateUser, deleteUser, suspendUser, reactivateUser, getUserActivity, getUserStats, checkAdminPermissions, // Conversations getConversationHistory, // Query executeQuery, executeQueryWithConfig, // File Upload uploadFile, // Low-level flowstackFetch, } from 'flowstack-sdk';
Default time-to-live values for the SDK's per-resource client-side cache.
utility, utilities, Flowstack SDK, getCached, setCached, deleteCached
Remove a value from the generic per-resource client-side cache.
utility, utilities, Flowstack SDK, getCached, setCached, CACHE_TTL
The low-level fetch escape hatch used by every API client function for authenticated requests to the Flowstack backend.
utility, utilities, Flowstack SDK, apiClient, executeQuery
Read a value from the generic per-resource client-side cache.
utility, utilities, Flowstack SDK, setCached, deleteCached, CACHE_TTL
import { // Generic getCached, setCached, deleteCached, CACHE_TTL, // Resource-specific getCachedWorkspaces, setCachedWorkspaces, invalidateWorkspacesCache, getCachedDatasets, setCachedDatasets, invalidateDatasetsCache, getCachedVisualizations, setCachedVisualizations, invalidateVisualizationsCache, getCachedReports, setCachedReports, invalidateReportsCache, getCachedSites, setCachedSites, invalidateSitesCache, // Bulk invalidation invalidateWorkspaceArtifacts, invalidateAllUserCache, } from 'flowstack-sdk';
Read the cached list of datasets from the client-side cache.
utility, utilities, Flowstack SDK, setCachedDatasets, invalidateDatasetsCache, useDatasets
Read the cached list of reports from the client-side cache.
utility, utilities, Flowstack SDK, setCachedReports, invalidateReportsCache, useReports
Read the cached list of sites from the client-side cache.
utility, utilities, Flowstack SDK, setCachedSites, invalidateSitesCache, useSites
Read the cached list of visualizations from the client-side cache.
utility, utilities, Flowstack SDK, setCachedVisualizations, invalidateVisualizationsCache, useVisualizations
Read the cached list of workspaces from the client-side cache.
utility, utilities, Flowstack SDK, setCachedWorkspaces, invalidateWorkspacesCache, useWorkspace
Clear the entire client-side cache for the current user.
utility, utilities, Flowstack SDK, invalidateWorkspaceArtifacts
utility, utilities, Flowstack SDK, getCachedDatasets, setCachedDatasets, invalidateWorkspaceArtifacts
utility, utilities, Flowstack SDK, getCachedReports, setCachedReports, invalidateWorkspaceArtifacts
utility, utilities, Flowstack SDK, getCachedSites, setCachedSites, invalidateAllUserCache
Invalidate the cached list of visualizations.
utility, utilities, Flowstack SDK, getCachedVisualizations, setCachedVisualizations, invalidateWorkspaceArtifacts
Invalidate all cached artifacts (datasets, visualizations, reports, etc.) for a workspace.
utility, utilities, Flowstack SDK, invalidateAllUserCache, invalidateWorkspacesCache
Invalidate the cached list of workspaces.
utility, utilities, Flowstack SDK, getCachedWorkspaces, setCachedWorkspaces, invalidateWorkspaceArtifacts
Parse a single Server-Sent Events line into a typed StreamEvent.
utility, utilities, Flowstack SDK, parseSSEStream, processSSEStream, useAgent
const event = parseSSELine(line: string)
const event = parseSSELine('data: {"type":"content","content":"Hello"}');
Async generator that yields typed StreamEvents from an agent SSE Response.
utility, utilities, Flowstack SDK, parseSSELine, processSSEStream, useAgent, post-stream
for await (const event of parseSSEStream(response)) { ... }
for await (const event of parseSSEStream(response)) { switch (event.type) { case 'content': console.log(event.content); break; case 'tool_start': console.log('Tool:', event.tool); break; case 'tool_result': console.log('Result:', event.result); break; case 'done': console.log('Complete'); break; } }
High-level helper that drives an agent SSE Response through callbacks.
utility, utilities, Flowstack SDK, parseSSEStream, parseSSELine, useAgent, post-stream
await processSSEStream(response, callbacks)
await processSSEStream(response, { onContent: (text) => appendMessage(text), onToolStart: (tool) => showToolIndicator(tool), onDone: () => finalize(), });
Write a value into the generic per-resource client-side cache with TTL.
utility, utilities, Flowstack SDK, getCached, deleteCached, CACHE_TTL
Write the list of datasets into the client-side cache.
utility, utilities, Flowstack SDK, getCachedDatasets, invalidateDatasetsCache, useDatasets
Write the list of reports into the client-side cache.
utility, utilities, Flowstack SDK, getCachedReports, invalidateReportsCache, useReports
Write the list of sites into the client-side cache.
utility, utilities, Flowstack SDK, getCachedSites, invalidateSitesCache, useSites
Write the list of visualizations into the client-side cache.
utility, utilities, Flowstack SDK, getCachedVisualizations, invalidateVisualizationsCache, useVisualizations
Write the list of workspaces into the client-side cache.
utility, utilities, Flowstack SDK, getCachedWorkspaces, invalidateWorkspacesCache, useWorkspace
HTTP + SSE API for Flowstack SDK: 337 operations.