Embeddable SDK (@flow/embed)
@flow/embed lets you add member sign-in, plus typed storefront and
members clients, to your own website — with one script tag. Members type
their password inside a sign-in widget served from our origin (an iframe), so
your page's code never touches their credentials. On success your page receives a
short-lived member token over an origin-pinned channel and can call the
Members API as the signed-in member.
Include the SDK
Script tag (browser global)
<script src="https://embed.example.com/flow.js"></script>
<script>
// window.Flow is now available
Flow.configure({
storefrontApiUrl: "https://storefront.example.com/v1",
participantApiUrl: "https://api.example.com",
});
</script>
Or as a module
import { Flow, configure, mountSignIn, storefront, members } from "@flow/embed";
configure(config) sets SDK-wide defaults once at boot so you don't repeat them
on every call:
configure({
embedOrigin: "https://embed.example.com", // where the widget + script are served (defaults to the script's origin)
storefrontApiUrl: "https://storefront.example.com/v1",
participantApiUrl: "https://api.example.com",
});
Read public data — Flow.storefront(pk)
A typed, read-only client backed by your publishable key. Safe to run in the browser.
const shop = Flow.storefront("flow_pk_your_publishable_key");
await shop.listClasses(); // StorefrontClass[]
await shop.listInstructors(); // StorefrontInstructor[]
await shop.listPlans(); // StorefrontPlan[]
await shop.config(); // StorefrontConfig — { providerId, slug, name, allowedOrigins }
These map onto the Storefront API reads.
Sign a member in — Flow.mountSignIn(el, options)
Mount the sign-in widget into a container element. The member signs in inside the widget; your page is notified and gets a token getter.
const handle = Flow.mountSignIn(document.getElementById("signin"), {
pk: "flow_pk_your_publishable_key",
studio: "your-studio-slug", // which studio the member belongs to
theme: { "--flow-color-accent": "#7c5cff" }, // optional brand overrides
});
mountSignIn returns a SignInHandle:
| Method | Description |
|---|---|
onSignIn(cb) |
Fires when a member signs in; cb(member) receives { id, email, name }. Returns an unsubscribe. |
onSignOut(cb) |
Fires when the member signs out. |
getMember() |
The current member, or null. |
getToken() |
Resolves to a usable short-lived access token (silently re-minted). |
members() |
A Members API client bound to this session (token + key attribution). |
signOut() |
Signs the member out (clears the session on our origin). |
destroy() |
Removes the widget and its listeners. |
Register your origins first. Member tokens are only ever delivered to an origin you have registered for the studio (in your dashboard). An unregistered page can read public storefront data but can never receive a member token — embedding sign-in fails closed.
Act as the member — Flow.members(getToken)
A typed Members API client. Get one either from the sign-in handle
(handle.members()) or directly from a token getter:
const me = handle.members();
// or: const me = Flow.members(() => handle.getToken(), { pk: "flow_pk_…" });
// reads
await me.me(); // Me — { id, email, name, activeProviderId }
await me.memberships(); // Membership[]
await me.bookings(); // Booking[]
// writes (each carries an Idempotency-Key automatically)
await me.book(occurrenceId, { channel: "online" }); // Booking
await me.cancel(bookingId); // void
await me.updateMe({ name: "Sam Lee", notifyByEmail: true });
await me.purchaseDropIn(occurrenceId); // CheckoutResult
await me.subscribe(planId, { couponCode: "WELCOME" }); // CheckoutResult
Every write generates a fresh Idempotency-Key per call (safe for network
retries). To dedupe across separate calls — for example a double-clicked "Buy" —
pass a stable one: me.book(id, { idempotencyKey: "stable-key" }).
A CheckoutResult is { orderId, status, redirectUrl? }. When status is
"pending", send the member to redirectUrl to complete payment; "succeeded"
means access was granted immediately.
The token model
- The member's password is entered inside the widget (our origin) and never reaches your page's DOM or JavaScript.
- Your page receives a short-lived access token plus a public member summary
(
{ id, email, name }) — nothing more. - There is no refresh token on your page.
getToken()silently re-mints a fresh access token from the widget when the current one is near expiry. - Tokens are delivered over an origin-pinned channel: a token is only ever sent to a specific origin you registered — never broadcast.
- Every Members API call also sends your publishable key as an attribution header
(
x-flow-pk) for rate-limiting and your activity log. It is not the auth credential — the member's token is.
A minimal working page
<!doctype html>
<html>
<head><meta charset="utf-8" /><title>My studio</title></head>
<body>
<div id="signin" style="max-width: 360px"></div>
<pre id="out">Sign in to load your bookings…</pre>
<script src="https://embed.example.com/flow.js"></script>
<script>
Flow.configure({
storefrontApiUrl: "https://storefront.example.com/v1",
participantApiUrl: "https://api.example.com",
});
const handle = Flow.mountSignIn(document.getElementById("signin"), {
pk: "flow_pk_your_publishable_key",
studio: "your-studio-slug",
});
handle.onSignIn(async (member) => {
const me = handle.members();
const bookings = await me.bookings();
document.getElementById("out").textContent =
`Welcome ${member.name ?? member.email} — ${bookings.length} bookings`;
});
handle.onSignOut(() => {
document.getElementById("out").textContent = "Signed out.";
});
</script>
</body>
</html>
Next
- Quickstart — issue keys and make raw API calls.
- Storefront API reference · Management API reference