Embed Companion in Proxy Mode
Overview
Deploy a Companion widget on any website with the delegation token held server-side. The browser never sees the token — only the deployment_id and surface_context travel in the request.
When to use
- Public-facing websites where you cannot expose credentials in HTML
- Customer portals, developer docs, support sites
- Any deployment where you need origin validation (CORS whitelist per deployment)
Prerequisites
- A
companion_deploymentsrecord with a minted delegation token - A proxy endpoint on your host server (
/api/companion/ask)
Create the deployment
# CLI
human companion deployment create \
--name my-site \
--surface-label my-site \
--allowed-origins "https://yoursite.com"
# Get the ready-to-paste snippet
human companion deployment snippet my-site
Proxy endpoint (Next.js App Router)
// app/api/companion/ask/route.ts
import { NextRequest, NextResponse } from 'next/server';
const HUMAN_API_URL = process.env.HUMAN_API_URL ?? 'https://api.haio.run';
const INTERNAL_API_TOKEN = process.env.INTERNAL_API_TOKEN!;
export async function POST(req: NextRequest) {
const body = await req.json() as {
text: string;
deployment_id?: string;
surface_context?: Record<string, unknown>;
};
// Fetch deployment record (server-side; never expose delegation_token to browser)
const depRes = await fetch(
`${HUMAN_API_URL}/v1/companion/deployments/${body.deployment_id}`,
{ headers: { Authorization: `Bearer ${INTERNAL_API_TOKEN}` } }
);
const deployment = await depRes.json() as {
delegation_token: string;
allowed_origins: string[];
};
// Validate origin
const origin = req.headers.get('origin') ?? '';
if (!deployment.allowed_origins.includes(origin) && deployment.allowed_origins.length > 0) {
return NextResponse.json({ error: 'Origin not allowed' }, { status: 403 });
}
// Forward to HumanOS with the server-side token
const agentRes = await fetch(`${HUMAN_API_URL}/v1/agents/call`, {
method: 'POST',
headers: {
Authorization: `Bearer ${deployment.delegation_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
target: 'agent://org/human/companion@0.1',
input: {
text: body.text,
surface_context: {
...(body.surface_context ?? {}),
deployment_id: body.deployment_id,
},
},
}),
});
const data = await agentRes.json();
return NextResponse.json(data);
}
Embed snippet (paste before </body>)
<script src="https://api.haio.run/cdn/companion-widget.js"></script>
<script>
HUMAN.Companion.init({
agentsCallUrl: window.location.origin + '/api/companion/ask',
buildAgentInput: () => ({
deployment_id: 'dep_abc123',
surface_context: {
surface: 'my-site',
page: window.location.pathname,
page_title: document.title,
},
}),
ui: { theme: 'auto', position: 'bottom-right' },
});
</script>
React / Next.js usage
import { CompanionWidget } from '@human/companion-widget/react';
export function Layout({ children, pathname }) {
return (
<>
{children}
<CompanionWidget
humanApiUrl={process.env.NEXT_PUBLIC_API_URL}
agentsCallUrl="/api/companion/ask"
buildAgentInput={() => ({
deployment_id: process.env.NEXT_PUBLIC_COMPANION_DEPLOYMENT_ID,
surface_context: {
surface: 'my-site',
page: pathname,
page_title: document.title,
},
})}
ui={{ theme: 'dark', position: 'bottom-right' }}
/>
</>
);
}