Skip to main content

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

  1. A companion_deployments record with a minted delegation token
  2. 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' }}
      />
    </>
  );
}

See Also

← All patterns