App Registration
Consumer apps (like Socialite, VideoMaker, etc.) register with Fabric as Apps — first-class citizens that get their own client credentials, white-label auth, and app-scoped tenancy.
1. Register Your App
Section titled “1. Register Your App”cargo run -- setup \ --app-name "My App" \ --email "dev@myapp.com" \ --password "SecureP@ss1"Output:
Fabric setup complete.
App: My App (my-app) Admin: dev@myapp.com Organization: My App (swift-fox-742) Organization ID: 5fe20183-...
Add to your consumer app .env:
FABRIC_API_URL=http://localhost:3001 FABRIC_CLIENT_ID=foc_abc123... FABRIC_CLIENT_SECRET=focs_xyz789... FABRIC_WEBHOOK_SECRET=whsec_a1b2c3d4...
API Key (for direct access): fab_def456...First, create a developer account:
# Sign up a developer accountcurl -X POST https://gofabric.dev/v1/auth/signup \ -H "Content-Type: application/json" \ -d '{"email":"dev@myapp.com","password":"SecureP@ss1"}'Save the access_token from the response, then register your app:
# Register the app — returns client_id + client_secretcurl -X POST https://gofabric.dev/v1/apps \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -d '{"name":"My App","auto_create_org":true}'Response:
{ "data": { "id": "a1b2c3d4-...", "name": "My App", "slug": "my-app", "client_id": "foc_abc123...", "client_secret": "focs_xyz789...", "auto_create_org": true, "allowed_origins": [], "created_at": "2026-04-05T12:00:00Z" }}2. Configure Your Consumer App
Section titled “2. Configure Your Consumer App”export FABRIC_API_URL=http://localhost:3001export FABRIC_CLIENT_ID=foc_abc123...export FABRIC_CLIENT_SECRET=focs_xyz789...export FABRIC_WEBHOOK_SECRET=whsec_a1b2c3d4...3. Initialize the SDK
Section titled “3. Initialize the SDK”import { FabricClient } from "@fabric-platform/sdk";
const fabric = new FabricClient({ baseUrl: process.env.FABRIC_API_URL, app: { clientId: process.env.FABRIC_CLIENT_ID!, clientSecret: process.env.FABRIC_CLIENT_SECRET!, },});import osfrom fabric_platform import FabricClient
fabric = FabricClient( base_url=os.environ["FABRIC_API_URL"], client_id=os.environ["FABRIC_CLIENT_ID"], client_secret=os.environ["FABRIC_CLIENT_SECRET"],)# All curl examples below use these variablesexport FABRIC_CLIENT_ID="foc_abc123..."export FABRIC_CLIENT_SECRET="focs_xyz789..."User Authentication
Section titled “User Authentication”Users sign up and log in through your app’s own UI. Fabric is invisible to them.
// Your signup handlerapp.post("/signup", async (req, res) => { const { email, password } = req.body;
// Fabric creates the user + personal org scoped to your app const auth = await fabric.auth.signup(email, password);
// Store the tokens in your session/cookies res.cookie("access_token", auth.access_token); res.json({ user: auth.user });});
// Your login handlerapp.post("/login", async (req, res) => { const auth = await fabric.auth.login(req.body.email, req.body.password); res.cookie("access_token", auth.access_token); res.json({ user: auth.user });});# Your signup handler@app.post("/signup")async def signup(email: str, password: str): auth = fabric.auth.signup(email, password) return {"user": auth["user"], "access_token": auth["access_token"]}
# Your login handler@app.post("/login")async def login(email: str, password: str): auth = fabric.auth.login(email, password) return {"user": auth["user"], "access_token": auth["access_token"]}# Signup — scoped to your app via X-Fabric-App-Idcurl -X POST https://gofabric.dev/v1/auth/signup \ -H "Content-Type: application/json" \ -H "X-Fabric-App-Id: $FABRIC_CLIENT_ID" \ -d '{"email":"alice@example.com","password":"UserP@ss1"}'
# Logincurl -X POST https://gofabric.dev/v1/auth/login \ -H "Content-Type: application/json" \ -H "X-Fabric-App-Id: $FABRIC_CLIENT_ID" \ -d '{"email":"alice@example.com","password":"UserP@ss1"}'Both return { "data": { "user": {...}, "access_token": "..." } }.
Acting on Behalf of Users
Section titled “Acting on Behalf of Users”Your server needs to submit workflows, manage resources, etc. for specific users. Use token exchange to get a user-scoped client.
// Get a user-scoped client via token exchangeconst userFabric = await fabric.asUser(userId);
// Everything is now scoped to this user's orgawait userFabric.workflows.runs.submit("video/ai_shorts", { input: { topic: "AI trends" },});
const orgs = await userFabric.organizations.list(); // Only this app's orgsToken exchange happens internally — the SDK caches user tokens and auto-refreshes them.
# Get a user-scoped client via token exchangeuser_fabric = fabric.as_user(user_id)
# Everything is now scoped to this user's orguser_fabric.workflows.runs.submit("video/ai_shorts", input={ "topic": "AI trends",})
orgs = user_fabric.organizations.list() # Only this app's orgs# Exchange app credentials for a user-scoped tokenTOKEN=$(curl -s -X POST https://gofabric.dev/v1/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "token_exchange", "client_id": "'$FABRIC_CLIENT_ID'", "client_secret": "'$FABRIC_CLIENT_SECRET'", "subject_token": "'$USER_ID'", "subject_token_type": "urn:fabric:user_id" }' | jq -r '.data.access_token')
# Use the token — all requests are now scoped to this usercurl -H "Authorization: Bearer $TOKEN" \ https://gofabric.dev/v1/organizations
curl -X POST https://gofabric.dev/v1/workflows/run?name=video/ai_shorts \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"input":{"topic":"AI trends"}}'Webhooks
Section titled “Webhooks”Register a webhook to receive push notifications when workflows complete, fail, or reach other terminal states. Your app should self-register on startup using the webhook secret from fabric setup:
// On app startup — register webhook if not already presentconst webhooks = await fabric.webhooks.list(orgId);const exists = webhooks.items?.some(w => w.url.includes("/api/webhooks/fabric"));if (!exists) { const result = await fabric.webhooks.create(orgId, { url: `${process.env.PUBLIC_URL}/api/webhooks/fabric`, event_filter: ["workflow.run.completed", "workflow.run.failed"], }); // result.secret is the signing secret (whsec_...) — returned once. // For local dev, fabric setup already provided it as FABRIC_WEBHOOK_SECRET.}# On app startup — register webhook if not already presentwebhooks = fabric.list_webhooks(org_id)exists = any("/api/webhooks/fabric" in w["url"] for w in webhooks)if not exists: result = fabric.create_webhook( org_id=org_id, url=f"{os.environ['PUBLIC_URL']}/api/webhooks/fabric", events=["workflow.run.completed", "workflow.run.failed"], ) # result["secret"] is the signing secret (whsec_...) — returned once.See the Webhooks Reference for signature verification, delivery semantics, and the full event catalog.
App Isolation
Section titled “App Isolation”Each app’s data is completely isolated:
- Orgs created through App A are invisible to App B
- Same org slug is allowed across different apps
- Users can have accounts in multiple apps (same Fabric identity, separate orgs)
- Workflows, assets, artifacts are scoped through orgs — automatically isolated per app
Managing Your App
Section titled “Managing Your App”// List your appsconst apps = await fabric.apps.list();
// Update settingsawait fabric.apps.update(appId, { auto_create_org: false, allowed_origins: ["https://myapp.com"],});
// Rotate client secret (one-time reveal)const { client_secret } = await fabric.apps.rotateSecret(appId);
// View aggregate usageconst usage = await fabric.apps.usage(appId);console.log(`${usage.total_runs} runs across ${usage.total_orgs} orgs`);# List your appsapps = fabric.apps.list()
# Update settingsfabric.apps.update(app_id, auto_create_org=False, allowed_origins=["https://myapp.com"])
# Rotate client secret (one-time reveal)new_creds = fabric.apps.rotate_secret(app_id)print("New secret:", new_creds["client_secret"])
# View aggregate usageusage = fabric.apps.usage(app_id)print(f"{usage['total_runs']} runs across {usage['total_orgs']} orgs")# List your appscurl -H "Authorization: Bearer $ACCESS_TOKEN" \ https://gofabric.dev/v1/apps
# Update settingscurl -X PATCH https://gofabric.dev/v1/apps/$APP_ID \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"auto_create_org":false,"allowed_origins":["https://myapp.com"]}'
# Rotate client secret (save the new secret — shown only once)curl -X POST https://gofabric.dev/v1/apps/$APP_ID/rotate-secret \ -H "Authorization: Bearer $ACCESS_TOKEN"
# View aggregate usagecurl -H "Authorization: Bearer $ACCESS_TOKEN" \ https://gofabric.dev/v1/apps/$APP_ID/usageAuto-Org on Signup
Section titled “Auto-Org on Signup”By default, every new user gets a personal org when they sign up through your app. This org is scoped to your app and the user is the owner.
Disable it if you want users to be invited to existing orgs instead:
await fabric.apps.update(appId, { auto_create_org: false });fabric.apps.update(app_id, auto_create_org=False)curl -X PATCH https://gofabric.dev/v1/apps/$APP_ID \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"auto_create_org":false}'Architecture
Section titled “Architecture”Your App (e.g., Socialite) |-- client_id + client_secret (for server-to-server) | |-- User: alice@example.com | |-- Org: "Alice's Agency" (app-scoped) | | |-- Team: "Marketing" | | +-- Team: "Creative" | +-- Org: "Client Corp" (invited) | +-- User: bob@example.com +-- Org: "Bob's Studio" (app-scoped)Fabric provides the entire identity + tenancy layer. Your app doesn’t need to implement auth, orgs, teams, invitations, or RBAC.