Documentation
Deploy your Gamaro Agent on your site. Script tag or NPM, your choice.
What is a Gamaro Agent?
Your data (knowledge base, imported documents) → Agent
Agent → events → Your site · your analytics
The Agent lives in the portal
You configure it once (vertical, brand, objectives). The widget is its display channel, not the Agent itself.
Your data grounds the answers
Import your documents (PDF, TXT, Markdown) into the knowledge base: the Agent refers to them when answering, rather than to an LLM's public training data. Qualification rules and offers are configured in the portal.
The publishable key ties it together
A pk_* key binds the embedded widget to an Agent and its configuration, resolved at load time.
Choose your approach
Ideal for: Static sites, CMSs that accept a script tag (WordPress, Shopify…)
SeeIdeal for: React, Next.js, Vue
SeeIdeal for: Discovery, evaluation
TryYour first Agent
1. Create the Agent in the portal
In /app/portal, create an Agent: give it a name and pick its vertical (real estate, B2B…).
2. Grab a test key
Generate a pk_test_* key from /app/portal/api-keys. It works on localhost with no domain configuration.
3. Embed the widget
Paste the script tag (below) with your key into a locally-served page. No build step.
4. Converse
Open the page: the Agent starts the conversation. Hook the ready or qualified event to react on your side.
Script Tag (CDN)
<!-- Add this script to your page -->
<!-- Production tip: pin a version + SRI, e.g. @0.2.0 + integrity="sha384-..." -->
<script
src="https://unpkg.com/@gamaro.io/widget@latest/dist/widget.umd.js"
data-api-key="pk_your_key"
></script>Environments: dev → staging → prod
The widget intentionally splits two kinds of publishable keys. A test key (pk_test_*) is designed for local development: it always works on localhost, 127.0.0.1 and *.localtest.me, with zero domain configuration. A live key (pk_live_*) is designed for public environments: it refuses localhost and requires an explicit domain whitelist.
1. Local development
Key: pk_test_*- Domain: no declaration required — test keys always allow localhost, 127.0.0.1 and *.localtest.me.
Create a test key from /app/portal/api-keys, embed the widget on http://localhost:3000 (or your dev server port), and exercise every feature without touching the Domains page.
2. Staging / preview
Key: pk_live_* (dedicated to staging)- Domain to declare: https://staging.mysite.com (exact URL of your preview environment)
Create a live key dedicated to staging — never reuse the production key. Add the exact URL of your preview environment in /app/portal/domains. If your staging runs on a custom port (e.g. preview-42.mysite.com:8443), add the full URL with port.
3. Production
Key: pk_live_*- Domain to declare: https://mysite.com (or wildcard *.mysite.com to cover several subdomains at once)
For a site that serves several subdomains (app, www, checkout…), a *.mysite.com wildcard is more maintainable than multiple exact entries. Note: the wildcard only covers direct subdomains — add mysite.com as an extra entry if the apex domain also serves the widget.
Why split test and live?
A pk_live_* live key refuses localhost even in dev and requires a domain whitelist. This is intentional: if a live key is stolen (leak in a public GitHub repo, compromised browser extension, accidental screenshot), it cannot be used from a local HTML file or from any undeclared third-party site. An attacker who gets the key cannot do anything with it without also controlling one of the domains you explicitly allowed. This is the seat belt that compensates for the key being publicly exposed in your pages' HTML.
Wildcard syntax
A *.mysite.com wildcard matches app.mysite.com, www.mysite.com, checkout.mysite.com — but not mysite.com itself. To cover the apex domain, add it as a separate entry. Wildcards only support one level (*.mysite.com ≠ **.mysite.com): a.b.mysite.com is not matched, you need to add *.b.mysite.com as well.
What happens if the domain is not allowed?
The Gamaro server returns a 403 ORIGIN_NOT_ALLOWED error and the widget stays silent (no chat window opens). Enable the widget's debug option during integration to see the response in the console — it is usually the first thing to check when the widget fails to appear in staging.
NPM + TypeScript
npm install @gamaro.io/widgetimport { createWidget } from "@gamaro.io/widget";
const widget = createWidget({ apiKey: "pk_your_key" });
// Control the widget
widget.open();
widget.close();
widget.reset();Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| data-api-key | string | required | Publishable API key (pk_*) — required, identifies your Agent |
| data-container | string | - | CSS selector for the inline mode container (e.g., '#chat'). The programmatic SDK also accepts a raw HTMLElement. |
| data-base-url | string | Production URL | Base URL for the embed |
| data-debug | boolean | false | Enable debug logging |
| data-email | string | - | Pre-identified visitor email (HMAC verification) |
| data-user-id | string | - | Host-side unique user identifier (HMAC verification) |
| data-user-hash | string | - | HMAC-SHA256 of the userId (or email as fallback), signed with the identity secret |
Real-world Examples
Display Modes
Floating
The widget appears as a floating button in a corner of the page. This is the default mode — no HTML option required, position is set in /app/portal/widget.
Inline
The widget is embedded directly into a container on your page. Ideal for a contact page or dedicated landing page. Pass a CSS selector via data-container, or an HTMLElement via the programmatic SDK.
import { createWidget } from "@gamaro.io/widget";
// container accepts a CSS selector OR an HTMLElement
const widget = createWidget({
apiKey: "pk_your_key",
container: "#chat-container",
});Multiple Agents on one page
Script Tags
<!-- B2B qualification widget -->
<script
src="https://unpkg.com/@gamaro.io/widget@latest/dist/widget.umd.js"
data-api-key="pk_your_b2b_key"
></script>
<!-- Real estate widget (same page) -->
<script
src="https://unpkg.com/@gamaro.io/widget@latest/dist/widget.umd.js"
data-api-key="pk_your_immo_key"
></script>Programmatic API
import { createWidget } from "@gamaro.io/widget";
// Each API key maps to an Agent with its own vertical, theme, and
// position — all configured from the admin portal.
const b2bWidget = createWidget({ apiKey: "pk_your_b2b_key" });
const immoWidget = createWidget({
apiKey: "pk_your_immo_key",
// container pins the widget to a specific DOM node (inline mode).
container: document.getElementById("demo-immo"),
});
// Access instances after they're ready
b2bWidget.on("ready", () => {
// window.GamaroApps.get(b2bWidget.applicationId) → GamaroAPI
console.log("B2B widget ready:", b2bWidget.applicationId);
});Theming
Theming is configured in the portal
Since release 2024.11, theming (colors, typography, border radius, light/dark scheme, position) is managed from /app/portal/widget. The widget resolves these settings at load time via the publishable key — no HTML changes required.
Open portal →Toggle light/dark at runtime
To sync the widget's color scheme with your host site (user toggle, system preference), use the JavaScript API window.Gamaro.setColorScheme('light' | 'dark' | 'auto').
Data Sources
Gamaro connects to your data via a generic interface. Each source declares its capabilities, and AI tools adapt automatically.
DataSource Interface
The TypeScript interface a data source implements. The implementation is built with the Gamaro team, then deployed server-side.
interface CoverageZone {
readonly label: string;
readonly departmentCodes: readonly string[];
readonly countryCode?: string;
}
interface DataSource {
readonly id: string;
readonly configKey?: string;
readonly displayName: string;
readonly coverageDescription: string;
readonly coverage?: CoverageZone;
getCapabilities(): ReadonlySet<DataSourceCapability>;
search?(params: Record<string, unknown>): Promise<unknown[]>;
getStats?(zone: string): Promise<Record<string, unknown>>;
getAreaMetrics?(zone: string, filters: Record<string, unknown>): Promise<Record<string, unknown>>;
estimate?(params: Record<string, unknown>): Promise<Record<string, unknown>>;
count?(areas: string[], filters: Record<string, unknown>): Promise<number>;
checkCoverage?(zone: string, coverage?: CoverageZone): Promise<{ covered: boolean; message?: string }>;
listTopCitiesByInventory?(limit: number): Promise<
Array<{ city: string; count: number; minPrice: number; maxPrice: number }>
>;
}Capabilities
Each capability enables one or more AI tools in the conversation.
| Capability | Method | Tools | Description |
|---|---|---|---|
search | search() | searchProperties | Search items/products with filters |
stats | getStats() | getCityStats | Aggregated statistics by zone |
areaMetrics | getAreaMetrics() | getAreasOverview | Detailed metrics by zone (overview) |
estimate | estimate() | estimateProperty, estimateRent | Price or value estimation |
count | count() | getPropertyCount | Count with filters |
coverage | checkCoverage() | checkZoneCoverage | Zone coverage verification |
topInventory | listTopCitiesByInventory() | suggestCityCandidates | Cities ranked by inventory volume (fallback suggestion) |
Implementation example: REST API
An implementation example connected to a REST API, as the Gamaro team would build it with you.
class MyApiDataSource implements DataSource {
readonly id = "my-api";
readonly displayName = "Mon API Produits";
readonly coverageDescription = "Catalogue produit via API REST";
getCapabilities() {
return new Set(["search", "count"]);
}
async search(params) {
const res = await fetch(`https://api.example.com/products?${qs(params)}`);
return res.json();
}
async count(areas, filters) {
const res = await fetch("https://api.example.com/products/count", {
method: "POST",
body: JSON.stringify({ areas, filters }),
});
const data = await res.json();
return data.count;
}
}Graceful Degradation
When a source only supports certain capabilities, unsupported tools are automatically hidden. Partners don't need to configure anything on the tools side.
searchstatsareaMetricsestimatecountcoveragetopInventoryAccess modes
Gamaro adapts to your infrastructure. The custom mode (DataSource) is offered on request: it is implemented with the Gamaro team, then deployed. Native connectors and standard protocols are being explored.
Identity Verification
When to use
Identity verification is designed for authenticated areas where visitors are already logged in — SaaS dashboards, client portals, member areas. It is not intended for public marketing pages where visitors are anonymous.
Identity Levels
| Option | Type | Default | Description |
|---|---|---|---|
| Anonymous | Cookie / session ID | - | Default for all visitors — no setup required |
| Identified | Email shared in chat | - | Visitor shares their email during conversation |
| Verified | HMAC attested by host | - | Host site cryptographically attests the visitor's identity — zero friction |
Step 1: Compute HMAC server-side
The HMAC must be computed on your backend. Never expose the secret in client-side code.
Node.js
import crypto from "node:crypto";
// Sign the stable userId when you have one (recommended), else the email.
// Send data-email and data-user-hash (plus data-user-id when you sign it):
// verification is skipped without data-email.
const signedValue = user.id ?? user.email;
const hmac = crypto
.createHmac("sha256", process.env.GAMARO_IDENTITY_SECRET)
.update(signedValue)
.digest("hex");Python
import hmac
import hashlib
import os
# Sign the stable user id when you have one (recommended), else the email.
# Send data-email and data-user-hash (plus data-user-id when you sign it):
# verification is skipped without data-email.
signed_value = user.id or user.email
user_hash = hmac.new(
os.environ["GAMARO_IDENTITY_SECRET"].encode(),
signed_value.encode(),
hashlib.sha256,
).hexdigest()Step 2: Pass identity to the widget
Add the identity attributes to your widget script tag, or use the programmatic API after login.
Script tag
<script
src="https://unpkg.com/@gamaro.io/widget@latest/dist/widget.umd.js"
data-api-key="pk_live_..."
data-email="${user.email}"
data-user-id="${user.id}"
data-user-hash="${hmac}"
></script>Dynamic (post-login)
For single-page apps, identify the visitor after login without reloading the page.
// After user logs in, identify them dynamically
window.Gamaro.identify({
email: "user@example.com",
userId: "usr_123",
userHash: "<computed_hmac>",
});HMAC Input
The HMAC is computed on userId if provided (stable identifier, recommended), or email as fallback. Using userId means the HMAC stays valid even if the user changes their email.
Security
The identity secret must never appear in client-side code. Generate it in your Gamaro admin dashboard under Settings > Identity Verification.
Events
| Option | Type | Default | Description |
|---|---|---|---|
| ready | - | - | Widget is loaded and ready |
| open | - | - | Widget was opened |
| close | - | - | Widget was closed |
| message | WidgetMessage | - | New message in the conversation |
| profile-update | - | - | User profile was updated (signal only, no profile: the lead shows up in the portal's lead list) |
| qualified | - | - | Lead qualified (signal only, no profile: the lead shows up in the portal's lead list) |
| funnel-stage-change | { stage, previousStage } | - | The visitor moved to a new conversion funnel stage |
| cta-shown | { ctaType, objectiveId? } | - | The objective's call-to-action button is displayed (once per objective per conversation) |
| cta-clicked | { ctaType, objectiveId? } | - | The visitor clicked the objective's call-to-action button |
| lead-captured | { hasEmail, hasPhone } | - | A contact was saved (only tells whether an email and/or a phone was given, never the value). Not emitted if the agent's reply fails |
| quota-warning | { remaining, limit } | - | API quota is running low |
| quota-exceeded | - | - | API quota has been exceeded |
| error | { code, message } | - | An error occurred |
| identity-verified | { email, userId? } | - | Visitor identity was verified via HMAC |
Usage example
// Subscribe to events
window.Gamaro.on("ready", () => {
console.log("Agent is ready");
});
window.Gamaro.on("profile-update", () => {
console.log("Profile was updated"); // signal only, no data
});
window.Gamaro.on("qualified", () => {
console.log("Lead qualified"); // signal only, no data
// Lead details: see the Leads page of the Gamaro portal
});
window.Gamaro.on("identity-verified", (data) => {
console.log("Identity verified:", data.email);
});
window.Gamaro.on("quota-warning", (data) => {
console.warn("Quota low:", data.remaining, "/", data.limit);
});
window.Gamaro.on("error", (data) => {
console.error("Error:", data.code, data.message);
});JavaScript API
| Option | Type | Default | Description |
|---|---|---|---|
| open() | void | - | Open the widget |
| close() | void | - | Close the widget |
| reset() | void | - | Reset the conversation |
| setTheme(theme) | void | - | Change theme dynamically |
| setColorScheme(scheme) | void | - | Toggle the color scheme ('light' | 'dark' | 'auto') — handy for wiring the host site's own theme switch |
| on(event, handler) | void | - | Subscribe to an event |
| off(event, handler) | void | - | Unsubscribe from an event |
| destroy() | void | - | Remove widget and cleanup resources |
| identify(params) | void | - | Identify a visitor with HMAC verification |
| version | string | - | Current SDK version (read-only) |
| applicationId | string | "" | Agent ID (applicationId) resolved after load (empty until ready is emitted) |
| window.GamaroApps | Map<string, GamaroAPI> | - | Global map of widget instances — key: applicationId, value: GamaroAPI |