App SDK
The App SDK, @workstudio-inc/sdk/app, is for an app whose own users sign in themselves, such as a consumer web app or a phone app. Each person signs in on a hosted work.studio page, gets their own private install of your App, and works only with their own projects, conversations, records and files.
The people who use your App this way are not members of your organization, unless you make it members-only.
It ships in the same npm package as the embed client, version 0.11.x:
npm i @workstudio-inc/sdkTo host the App’s web front-end on work.studio, at https://<address>.workstudio.run, see Ship an App.
1. Turn on direct use for your App
Section titled “1. Turn on direct use for your App”You need an App in Flow first. See Apps and customers.
- In Flow, open Distribute › Apps and open your App.
- Select Advanced Settings, then the Direct use tab.
- Under Let people use this App directly, turn on Direct use and set an Instance key: lowercase letters, digits and dashes, such as
acme-notes. It can’t be changed once saved. - Under Agents people can talk to, choose the agents your users may chat with. Without one, people can sign in but have nothing to talk to.
- Under Sign-in, choose the methods: Google and Email and password. Apple sign-in isn’t offered on the sign-in page, even when Apple is ticked. On iPhone and iPad the sign-in page offers Email and password only, so keep it turned on if your App has an iPhone build.
- Under Where it runs, add each platform: Web (with its Origins and Redirects), iPhone and iPad (Bundle id) or Android (Package name).
- Optionally set the AI allowance in USD per person per month, and brand the Sign-in page (name, color, logo, support email, privacy and terms links).
- Select Save direct use.
After you save, the platform creates a sign-in client for each platform and shows its id under Where it runs. You need that client id in your code.
2. Sign people in
Section titled “2. Sign people in”import { createApp } from '@workstudio-inc/sdk/app';
const app = createApp({ apiUrl: 'https://api.work.studio', signInIssuer: 'https://auth.work.studio/realms/apps', clientId: '<client id from Where it runs>', appId: '<your App id>', // optional: lets signed-out visitors read the App's public content});
// At start-up. Does nothing unless the person is coming back from signing in.await app.auth.completeSignIn();
if (!(await app.auth.signedIn())) { await app.auth.signIn(); // opens the hosted sign-in page}Sign-in uses the authorization-code flow with PKCE on the hosted page. The SDK then swaps the result for a short-lived work.studio token and renews it for you.
- Phone apps: pass
openUrl(for example Capacitor’sBrowser.open) andredirectUri(your app’s scheme), callcompleteSignIn(url)with the deep link, and passstorageto keep the sign-in in secure storage. - Your own server acting for a signed-in person: pass
platformToken: () => tokeninstead of the sign-in settings.
3. Work with the person’s things
Section titled “3. Work with the person’s things”createApp() returns these parts. Every call is made as the signed-in person.
| Part | What it covers | Main methods |
|---|---|---|
app.auth |
Sign-in and the person’s token | signIn, completeSignIn, signedIn, onChange, signOut, token, renew |
app.account |
Who they are, their AI allowance, deleting their account | me, update, allowance, delete |
app.projects |
Their own projects | list, get, create, update, archive, delete |
app.chat |
Conversations with your App’s agents | start, list, history, rename, delete; a conversation has send and stop |
app.data |
Records in the collections your App declares, only this person’s | collection(name) with find, get, create, update, replace, delete |
app.content |
Read-only content your App ships, at the version their install runs | pack(name) with list, get, page, item |
app.files |
Their uploaded photos and PDFs | upload, list, url, delete |
app.images |
Pictures made from their images, charged to their allowance | make |
app.billing |
Their plan, if your App sells plans | plan, checkout, manage, refresh |
const project = await app.projects.create({ name: 'Kitchen side table' });
const chat = await app.chat.start({ agentId: DESIGNER_AGENT_ID, projectId: project.id });const reply = await chat.send('A walnut side table, 50 cm square, with a lower shelf', { onText: (piece) => render(piece),});
const notes = app.data.collection<{ title: string; status: string }>('acme_notes');const saved = await notes.create({ title: 'Side table', status: 'draft' });await notes.update(saved.id, { status: 'checked' }); // merges; replace() replacesA record can be up to 1 MB, counted as its JSON. A larger one isn’t saved: the call fails with status 413 and the code RECORD_TOO_LARGE. Keep pictures and documents in app.files, and save the file’s id in the record.
Stop an answer
Section titled “Stop an answer”conversation.stop() ends the answer that is being written, at once. What the agent wrote before the Stop stays in the conversation, and the turn ends with the status cancelled. What the answer used before the Stop is charged once; nothing it would have used after the Stop is charged. stop() returns stopped, stopping (another server is writing the answer and stops it within seconds) or not_running.
Errors
Section titled “Errors”| Error | Meaning |
|---|---|
SignInRequired |
The person needs to sign in again |
AllowanceUsed |
This month’s AI allowance is spent |
TurnFailed |
An answer ended in an error. notCharged says it wasn’t counted against the allowance |
AppError |
Anything else, with status, a message you can show and, when the platform gave one, a code |
What each error means and what to do about it: Troubleshoot App sign-in and data. The requests the SDK makes, to check one by hand: App HTTP requests.
Account deletion
Section titled “Account deletion”App stores require that people can delete their account inside the app. Call app.account.delete(). It also cancels any plan the person pays for.
Delete one of your App’s people
Section titled “Delete one of your App’s people”You can delete someone yourself, for example when they ask you by email. From the command line:
npx workstudio apps users listnpx workstudio apps users delete <accountId>delete says what will go and asks you to type the id to confirm; pass --yes when no one is at the keyboard. It needs an API key, or a person, with connection:delete in the App’s environment. The request behind it is DELETE /api/v1/workflow/connection/apps/{appId}/end-users/{accountId}.
About 16 minutes later, work.studio runs the clean-up a second time. A sign-in lasts up to 15 minutes, so this removes anything an App that was still open saved after the first pass. It happens after app.account.delete() too.
The person can sign in again later. They come back with the same account id and nothing in it. If they sign in again within those 16 minutes, the second clean-up is skipped, so anything their earlier sign-in saved in the meantime stays with them.
Selling plans
Section titled “Selling plans”An App can sell plans to its people through your own Stripe account, with Stripe’s hosted checkout and customer portal. A person’s plan sets their monthly AI allowance. Set the plans, connect Stripe and set up the stores on the app’s Plans & billing page, in the full Designer view (the app’s page, Business › Plans & billing; it opens once sign-in is on for the app). Plans can have a free trial, and checkout can take discount codes made in your Stripe dashboard (Allow discount codes at checkout). The same settings are in the API (PUT …/catalog/integrations/{id}/end-user-access/billing and …/billing/stripe-key).
Phone builds can sell the same plans through the stores: App Store and Google Play on the same page takes the App Store and Google Play details (bundle id, keys and the stores’ notification addresses), and a link from the app to your website’s plans page where the stores allow one. A full refund of a person’s latest invoice ends their plan at once; if they buy again, they get the new plan.
const plan = await app.billing.plan();await app.billing.checkout({ priceId: plan.plans[1].prices[0].id, successUrl: 'https://notes.acme.com/#welcome' });// Back at the success address:await app.billing.refresh();Phone app stores have their own rules for selling digital goods inside an app. Check them before you offer checkout in a phone build.