End to end with the widget
This is the whole path for an application you own and can change. You start with nothing and finish with an agent-operable flow on a verified production domain, plus a manifest in your repository that reproduces the setup.
Budget about an hour the first time. Every step says what to do by hand and what to say to a coding agent, so you can take either route or mix them.
What you need: an Appilot account (the beta is by invitation, so request one at appilot.space), a web application you can run locally, and a backend you can add one endpoint to.
1. Account and organization
Sign in at app.appilot.space. If your account belongs to no organization yet, create one from My Account: it becomes yours, you are its administrator, and it starts with trial credits.
If you were invited into an existing organization, you are already a member. Whether you can register apps and mint keys depends on your role. See Who can configure.
2. A hostname you can develop on
Do this before creating the app, because the app wants the hostname.
Serve your development build on a name that resolves to 127.0.0.1. The
simplest is lvh.me, which needs no setup:
http://myapp.lvh.me:3000
Bare localhost is not a registrable domain, and the fallback tier that uses a
test key gives you no app context. The reasoning is in
Develop on localhost.
3. The app, its domain and its key
In the Backoffice, create an app, add myapp.lvh.me as a domain, and create a
widget key for it. Mark the key a test key: a live key needs a verified domain,
and you have not verified anything yet.
Copy both halves the key gives you. The public key (wk_live_... or
wk_test_...) goes in your page. The secret (wsk_secret_...) goes in your
backend's environment and nowhere else. The secret is shown once.
From an MCP client the same thing is one sentence:
Create an app called Acme Booking for myapp.lvh.me and give me a test widget key with its secret.
create_app is idempotent: running it twice converges on the same app rather
than creating a second one, and it hands back the script tag and boot snippet
for the app it provisioned.
4. The identity relay
Appilot serves identified users, and your app is the source of identity. Add one endpoint to your backend that exchanges your session for an Appilot token.
npm install appilot-server
// app/api/widget/token/route.ts
import { createWidgetTokenHandler } from 'appilot-server';
export const POST = createWidgetTokenHandler({
apiUrl: 'https://api.appilot.space',
widgetKey: process.env.APPILOT_WIDGET_KEY!,
widgetSecret: process.env.APPILOT_WIDGET_SECRET!,
resolveUser: async request => {
const session = await getSession(request);
if (!session) return null;
return { externalId: `acme:${session.userId}`, displayName: session.name };
},
});
# .env.local
APPILOT_WIDGET_KEY=wk_test_...
APPILOT_WIDGET_SECRET=wsk_secret_...
Not on Node? The exchange is one HTTP call and The identity relay has it in Python and Ruby.
Check it before moving on. Signed out, your own endpoint must refuse:
curl -i -X POST http://myapp.lvh.me:3000/api/widget/token
# HTTP/1.1 401 {"error":{"code":"IDENTITY_REQUIRED"}}
5. Boot the widget
npm install appilot
import { bootAppilotWidget } from 'appilot';
bootAppilotWidget({
widgetScriptUrl: 'https://cdn.appilot.space/widget/v1/appilot.esm.js',
tokenEndpoint: '/api/widget/token',
});
No widget key: your page is on a registered domain, so the tenant comes from the domain. Call it once when your app mounts. It is idempotent while the widget is healthy, so a re-render does not boot a second one.
Load the page signed in. A launcher appears in the corner; open it and ask something. You are now talking to your own organization's assistant.
Nothing appears? Check the browser console, then the three usual causes: the
page is not on the registered hostname, your relay answered something other than
{ token, expiresIn }, or your user was signed out.
6. Make one capability agent-operable
An assistant that answers is useful. An assistant that acts is the point. Pick one thing your users do often, say opening a booking, and register it as a client action.
import { registerTool } from 'appilot';
useEffect(() => {
const handle = registerTool({
name: 'open_booking',
description: 'Open a booking by its id so the user can see and edit it.',
inputSchema: {
type: 'object',
properties: { booking_id: { type: 'string' } },
required: ['booking_id'],
},
annotations: { readOnlyHint: true },
async execute({ booking_id }) {
router.push(`/bookings/${booking_id}`);
return { content: [{ type: 'text', text: `Opened booking ${booking_id}.` }] };
},
});
return () => handle.unregister();
}, []);
Reload and ask the assistant to open a booking you know exists. It should
navigate. readOnlyHint: true says this only navigates; drop it on anything that
mutates and the user is asked to confirm first. See
Client actions.
For a capability that spans more than one screen, ask your MCP client for the full set:
Scaffold the agent-first artifacts for "cancel a booking" as cancel-booking.
scaffold_agent_first returns four things and the order to create them in: the
HTTP-proxy tool for the server-side half, the client action for the page half,
the action plan that is the procedure, and the knowledge article that carries
the meaning. Author them, then ask the assistant to do the thing end to end.
7. Verify the integration
Static review cannot see a stale bundle or a rotated key. Ask for the live check:
Verify the integration at http://myapp.lvh.me:3000, the relay is at /api/widget/token.
verify_integration loads the real page and reports whether the instance is
reachable, whether the domain resolves to your tenant, whether the widget boots,
whether the relay refuses an unauthenticated caller, and whether Appilot logged
console errors.
8. Put the manifest in git
Your app, its domains, its keys and its whole content model fit in one file you keep under version control:
// appilot.manifest.json
{
"kind": "appilot.app-manifest",
"formatVersion": "1.0",
"app": { "name": "Acme Booking" },
"domains": [{ "domain": "app.acme.com" }],
"widgetKeys": [{ "name": "production" }],
"config": { "kind": "appilot.config-bundle" }
}
Ask your client to export what you have built and write it there:
Export the config for this app and write it into appilot.manifest.json as the config section.
Secrets never travel in a bundle: it carries references, and the widget key secret stays where it was shown. Commit the file. Provisioning and configuration are now reviewed the way code is.
9. Promote to production
-
Register the production hostname (
app.acme.com) as a domain of the same app. -
Publish the TXT record and verify it. See Verify your domain.
-
Mint a live widget key covering the verified hostname. You only need it if something in your setup runs where the domain cannot resolve; on a registered domain the keyless boot you already have keeps working unchanged.
-
Point your production relay at the production key and secret.
-
Apply the manifest against production and read the plan before you accept it:
Plan the manifest in appilot.manifest.json, show me the diff, then apply it.
apply_manifestrefuses without a plan token derived from the exact manifest bytes that were previewed, so nothing is applied that nobody saw. -
Run
verify_integrationagainst the production URL.
What to build next
- Focused Sessions when the conversation itself is the unit of work.
- Theming and Layout modes to make the panel look like part of your product.
- Config backups for restore points and clones between environments.