Skip to main content

CDN Embed Integration

The fastest way to add Ozwell to any website. No build step, no framework required β€” just a single script tag.

The widget sends requests to the Ozwell API, which routes them to whichever AI backend is configured (LLM gateway, Ollama, or mock). No backend configuration is needed in the embed code itself.

Use https://api.ozwell.ai/widget for production embeds. The loader at https://ozwellapi.os.mieweb.org/widget is for development.

Quick Start​

Add this snippet to your HTML, just before the closing </body> tag:

<!-- Production loader -->
<script src="https://api.ozwell.ai/widget"></script>

That's it! A chat widget will appear in the bottom-right corner of your page.

Let Visitors Sign In​

The basic embed omits a host key. On a deployment with widget authentication enabled, visitors sign in through the widget.

Opening chat offers the methods configured by that deployment: Google, Apple, email one-time code, or the visitor's own agent/parent key. Google and Apple use a popup; allow popups for the widget origin. Host-configured agent or parent keys continue to work directly and never trigger this sign-in gate.

Sign-in authorizes the current session; it does not add persistent chat history or memory. User-entered keys are session-only by default. Remembering a key is explicit opt-in with a trusted-personal-device warning. Use the widget's forget-key/sign-out control to clear it. Browser storage may be blocked in third-party iframes; sign-in does not require remembering a key.

Conversations remain private to the user and Ozwell, which builds user trust. The host receives lifecycle signals and explicitly configured tool calls, not conversation content. Sharing conversation content is always opt-in.

Operators must configure credentials, email delivery, and account admission before enabling this mode. See the widget authentication deployment guide. The hosted environment may not offer these methods until that rollout is complete.

Optional: Configure an Agent​

To select an agent for the host page, configure an agent key issued by the same deployment as the loader. Define the configuration before loading the script:

<script>
window.OzwellChatConfig = { apiKey: 'agnt_key-your-agent-key' };
</script>
<script src="https://api.ozwell.ai/widget"></script>

Getting Your Credentials​

Use the Manager for the same deployment as your loader. The Manager at https://ozwellconsole.os.mieweb.org is the development environment; its keys are for the development loader at https://ozwellapi.os.mieweb.org/widget.

Option A: Agent Key​

Agent keys connect to a server-side agent definition that manages the system prompt, model, temperature, and allowed tools. This is the recommended embed path.

  1. Log in to the Manager for your selected deployment. For development, use development Ozwell Manager with MIE auth (manager.os.mieweb.org credentials).
  2. Ozwell automatically provisions your ozw_ parent key if you do not already have one.
  3. Create an agent in the manager UI.
  4. Copy the Agent Key from the response (starts with agnt_key-).
  5. Use it in the embed config.

Option B: Parent API Key​

Parent keys give you raw chat-completions access. Use this when the page provides the model, system prompt, and tools inline in window.OzwellChatConfig.

  1. Log in to the Manager for your selected deployment. For development, use development Ozwell Manager with MIE auth (manager.os.mieweb.org credentials).
  2. Use Show key to copy your parent key (starts with ozw_).
  3. Put the parent key, model, system prompt, and any page tools in window.OzwellChatConfig.

Configuration Options​

Configure the loader before loading the script:

<script>
window.OzwellChatConfig = {
apiKey: 'agnt_key-your-agent-key',
title: 'Support',
placeholder: 'Ask a question...',
welcomeMessage: 'How can I help?',
thinkingEnabled: true
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

Available Options​

Config propertyTypeDescription
apiKeystringAgent key (agnt_key-...) or parent key (ozw_...)
endpointstringChat-completions endpoint; defaults to the loader's origin
titlestringTitle in the host-page widget shell
placeholderstringComposer placeholder
welcomeMessagestringInitial assistant message
thinkingEnabledbooleanShows the thinking-mode control when reasoning is available
thinkingDefaultMode0 to 30 hidden, 1 collapsed, 2 auto, 3 expanded
defaultUIbooleanSet false when mounting into your own container
containerIdstringElement ID for a custom mount target

Proposed page control option (not implemented)​

The following pageControl setting is a proposal. It is not implemented in the current or released loader: adding this configuration alone does not activate page control.

For a host application whose root element is #app, the proposed setup would be:

<script>
window.OzwellChatConfig = {
pageControl: { root: '#app', visual: 'mask' }
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

With that proposed configuration, loading /widget would:

  1. Load the page controller and its styles on demand.
  2. Scope the controller to #app and register browser_snapshot, browser_click, browser_fill, browser_select, and browser_scroll with the existing iframe MCP bridge.
  3. Make those tools available to the assistant using the selected visual feedback. The loader would handle registration and dispatch alongside existing custom page tools.
Proposed settingDefaultChoices
pageControlOmitted (off)Supply the object to opt in.
rootRequiredSelector for one application container, with the widget outside it.
visual'mask''mask', 'highlight', 'both', or 'none'.

Mask would show the moving cursor and temporarily block manual input. Highlight would mark the target while leaving the page interactive. Both would combine these effects; none would hide them.

The current proof of concept, browser-tools.html, wires the controller explicitly. Its guided run, step-through controls, and speed selector remain demo features. See the canonical Proposed page control option for the design.


JavaScript API​

The embed script exposes a global OzwellChat object for programmatic control:

Open/Close the Widget​

// Open the chat window
OzwellChat.open();

// Close the chat window
OzwellChat.close();

// Toggle open/closed (coming soon)
// OzwellChat.toggle();

Read Page Data With Tools​

window.OzwellChatConfig = {
...window.OzwellChatConfig,
tools: [
{
type: 'function',
function: {
name: 'get_page_data',
description: 'Read current page data',
parameters: {
type: 'object',
properties: {}
}
}
}
]
};

document.addEventListener('ozwell-tool-call', (event) => {
const { name, respond } = event.detail;
if (name !== 'get_page_data') return;

respond({
userId: 'user_123',
page: window.location.pathname
});
});

// Send a message as the user (coming soon)
// OzwellChat.sendMessage('Hello, I need help with...');

Events​

Privacy Note: Ozwell respects user privacy. The host site receives only lifecycle eventsβ€”never conversation content. Users can ask anything without fear of surveillance.

// Widget is ready
window.addEventListener('ozwell:ready', () => {
console.log('Widget loaded');
});

// Chat window opened
window.addEventListener('ozwell:open', () => {
analytics.track('Chat Opened');
});

// Chat window closed
window.addEventListener('ozwell:close', () => {
analytics.track('Chat Closed');
});

// User explicitly shared data (opt-in only)
window.addEventListener('ozwell:user-share', (event) => {
// Only fires when user chooses to share
console.log('User shared:', event.detail);
});

⚠️ No message content events: ozwell:message and ozwell:user-message do not exist. Conversation content is private between the user and Ozwell.


Tool Calling​

This is the big one. Ozwell can do more than chat β€” it can take actions on your page. When a user says "update my email to bob@example.com," Ozwell's AI can call a function you define that actually updates the form field.

Here's how it works:

The Ozwell widget runs in a sandboxed iframe. It cannot touch your page directly β€” no DOM access, no cookies, no JavaScript variables. Instead, when the AI decides to use a tool, the widget sends an ozwell-tool-call event to your page. Your code runs the action and sends a result back. The AI then uses that result to respond to the user.

Step 1: Load the Widget with an Agent Key​

Your agent definition (created via the Agent Registration API) includes the tools Ozwell can offer. The widget discovers them automatically.

<script>
window.OzwellChatConfig = { apiKey: 'agnt_key-your-agent-key' };
</script>
<script src="https://api.ozwell.ai/widget"></script>

That's the same script tag from Quick Start. If your agent has tools defined, they just work β€” the widget discovers them during its MCP handshake with the server.

Step 2: Handle Tool Calls​

When the AI calls one of your tools, the loader dispatches an ozwell-tool-call DOM event on document. Listen for it and call respond() with the result:

document.addEventListener('ozwell-tool-call', (e) => {
const { name, arguments: args, respond } = e.detail;

if (name === 'update_form_data') {
// Do whatever you want β€” update inputs, call your own API, etc.
if (args.name) document.getElementById('input-name').value = args.name;
if (args.email) document.getElementById('input-email').value = args.email;

// Tell Ozwell what happened
respond({ success: true, updated: { name: args.name, email: args.email } });

} else if (name === 'get_form_data') {
// Tools can also READ from your page
respond({
success: true,
data: {
name: document.getElementById('input-name').value,
email: document.getElementById('input-email').value
}
});
}
});

You must call respond() or error(). The AI is waiting for the result. If you don't respond, the loader returns a tool error after a timeout.

Step 3: That's It​

There is no step 3. The loader handles the MCP protocol, JSON-RPC messages, and postMessage plumbing. You write normal JavaScript in a normal event listener.

What Goes in the Agent Definition vs. Your Page​

ConcernWhere It LivesWho Manages It
Tool names and descriptionsAgent definition (server)You, via the Agent API
Parameter schemas (what inputs the tool accepts)Agent definition (server)You, via the Agent API
System prompt ("you are a helpful assistant…")Agent definition (server)You, via the Agent API
What happens when a tool is calledYour page (client)Your JavaScript

The AI sees the tool name, description, and parameter schema to decide when to call a tool and what arguments to pass. Your page decides what to do with those arguments.

Define Page Tools Inline​

Use the agent key generated by Ozwell Manager, then add page-side tools directly in your page config when the agent needs to read from or write to the host page:

<script>
window.OzwellChatConfig = {
apiKey: 'agnt_key-your-agent-key',
tools: [{
type: 'function',
function: {
name: 'update_form_data',
description: 'Updates user profile fields on the page',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'New display name' },
email: { type: 'string', description: 'New email address' }
},
required: []
}
}
}]
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

tools[].function is the OpenAI-style tool schema: name, description, and JSON-schema parameters. Do not put executable JavaScript functions there. You still handle actual execution with ozwell-tool-call β€” the event listener code doesn't change.

Advanced: Parent Key Direct Chat​

Parent keys (ozw_...) can be used directly for chat completions when the page provides the chat configuration inline. In that mode, put the parent key, model, system prompt, and any page tools in window.OzwellChatConfig:

<script>
window.OzwellChatConfig = {
apiKey: 'ozw_your-parent-key',
model: 'qwen2.5-coder:3b',
system: 'You are a helpful assistant for managing user profiles.',
tools: [{
type: 'function',
function: {
name: 'update_form_data',
description: 'Updates user profile fields on the page',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'New display name' },
email: { type: 'string', description: 'New email address' }
},
required: []
}
}
}]
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

tools[].function is the OpenAI-style tool schema: name, description, and JSON-schema parameters. Do not put executable JavaScript functions there. You still handle actual execution with ozwell-tool-call β€” the event listener code doesn't change.

For normal embeds, prefer an agent key from Ozwell Manager so the prompt, model policy, and shared tools stay server-side.

Security: What Crosses the Iframe Boundary​

Understanding what data flows where:

DataCrosses the boundary?Direction
Tool call name + argumentsβœ… YesOzwell β†’ Your page
Tool result (your respond())βœ… YesYour page β†’ Ozwell
User's chat messages❌ NoStays in iframe
AI's text responses❌ NoStays in iframe
Your page's DOM, cookies, JS❌ NoStays on your page

The AI can only call tools you've defined. It cannot access your page's DOM, make arbitrary network requests from your origin, or read anything you haven't explicitly returned via respond().

Tips​

  • Write good tool descriptions. The AI reads them to decide when to use a tool. "Updates user profile fields on the page" is better than "updates stuff."
  • Validate arguments. The AI usually gets the schema right, but treat the incoming args like any untrusted input β€” check types and ranges before acting on them.
  • Return useful results. The AI uses respond() data to craft its reply. If you return { success: true }, the AI can only say "done." If you return { success: true, updated: { name: { from: "Alice", to: "Bob" } } }, the AI can confirm the details.
  • Use debug: true during development. It shows tool execution pills in the chat UI so you can see what's happening.

For the full postMessage protocol details (useful if you're building a custom integration without the loader), see the Embed Widget README. For the design inspiration behind this architecture, see MCP postMessage Standard.


Examples​

Widget URLs

Use the production API origin for the loader, iframe, and API requests:

  • Production loader: https://api.ozwell.ai/widget
  • Hosted iframe page: https://api.ozwell.ai/widget/frame/
  • Development loader: https://ozwellapi.os.mieweb.org/widget

Basic Embed​

<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome to My Site</h1>
<p>Content goes here...</p>

<!-- Ozwell Chat Widget -->
<script>
window.OzwellChatConfig = { apiKey: 'agnt_key-your-agent-key' };
</script>
<script src="https://api.ozwell.ai/widget"></script>
</body>
</html>

Custom Title and Welcome Message​

<script>
window.OzwellChatConfig = {
apiKey: 'agnt_key-your-agent-key',
title: 'Product Help',
welcomeMessage: 'What would you like to know?'
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

Enable Thinking Controls​

<script>
window.OzwellChatConfig = {
apiKey: 'agnt_key-your-agent-key',
thinkingEnabled: true,
thinkingDefaultMode: 2
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

Triggered by Button Click​

<button onclick="OzwellChat.open()">Chat with Us</button>

<script>
window.OzwellChatConfig = { apiKey: 'agnt_key-your-agent-key' };
</script>
<script src="https://api.ozwell.ai/widget"></script>

Complete Page with Tool Calling​

A full working example β€” an AI assistant that can read and update form fields on the page:

<!DOCTYPE html>
<html>
<head>
<title>My App β€” with Ozwell</title>
</head>
<body>
<h1>User Profile</h1>
<form id="profile-form">
<label>Name: <input type="text" id="input-name" value="Alice Johnson" /></label><br/>
<label>Email: <input type="email" id="input-email" value="alice@example.com" /></label><br/>
<label>Zip: <input type="text" id="input-zip" value="90210" /></label>
</form>

<!-- 1. Load Ozwell (agent key β€” tools defined server-side) -->
<script>
window.OzwellChatConfig = {
apiKey: 'agnt_key-your-agent-key',
welcomeMessage: 'Hi! I can view or update your profile. Just ask.',
debug: true // shows tool pills in chat β€” turn off in production
};
</script>
<script src="https://api.ozwell.ai/widget"></script>

<!-- 2. Handle tool calls -->
<script>
document.addEventListener('ozwell-tool-call', (e) => {
const { name, arguments: args, respond } = e.detail;

if (name === 'get_form_data') {
respond({
success: true,
data: {
name: document.getElementById('input-name').value,
email: document.getElementById('input-email').value,
zip: document.getElementById('input-zip').value
}
});

} else if (name === 'update_form_data') {
if (args.name) document.getElementById('input-name').value = args.name;
if (args.email) document.getElementById('input-email').value = args.email;
if (args.zip) document.getElementById('input-zip').value = args.zip;
respond({ success: true, updated: { name: args.name, email: args.email, zip: args.zip } });

} else {
respond({ success: false, error: `Unknown tool: ${name}` });
}
});
</script>
</body>
</html>

Try saying: "what's my name?" or "change my email to bob@example.com"


Security & Privacy​

Conversation Privacy​

πŸ” Conversations are private by default. The dialogue between users and Ozwell is never shared with the host site. Users can ask any questionβ€”even ones they might feel are "dumb"β€”knowing their conversation stays between them and Ozwell.

Sharing is always opt-in: only when a user explicitly chooses to share information does it become visible to the host site.

API Key Requirements​

⚠️ Every widget instance requires a valid API key.

Recommended: use Ozwell Manager to create an agent and copy its agnt_key-... key. The agent key points to the server-side persona, tools, and model policy for that agent.

Advanced: parent keys (ozw_...) can authenticate direct chat-completions/API requests when the page supplies the needed model/system/tool config in window.OzwellChatConfig.

The widget will display a clear error if no key is configured.

Domain Restrictions (Coming Soon)​

For additional security, configure domain restrictions for your scoped key:

  1. Go to Settings β†’ API Keys in your dashboard (coming soon)
  2. Edit your scoped key
  3. Add allowed domains under Domain Restrictions
  4. Only requests from listed domains will be accepted

Content Security Policy (CSP)​

If your site uses CSP headers, add Ozwell's domains:

Content-Security-Policy: 
script-src 'self' https://api.ozwell.ai https://cdn.ozwell.ai;
frame-src 'self' https://api.ozwell.ai;
connect-src 'self' https://api.ozwell.ai;

Troubleshooting​

Widget Not Appearing​

  1. Check the console for JavaScript errors
  2. Verify your API key is valid and has correct permissions
  3. Check domain restrictions if configured
  4. Ensure the script loads after the page content

Widget Appears But Chat Fails​

  1. Check the error message in the chat widget β€” it will show the specific auth error
  2. Verify your API key starts with agnt_key- or ozw_
  3. Verify the agent key exists in Ozwell Manager, or that your parent-key config includes a model and system prompt
  4. Review network tab for 401 responses

Styling Conflicts​

The widget renders in an iframe, so styling conflicts are rare. If you need to adjust the container:

/* Adjust the widget container position */
#ozwell-widget-container {
z-index: 9999 !important;
}

Next Steps​