# iraa.ai — Full Documentation
> DemoAgent by iraa.ai: AI-powered interactive product demo platform. Deploy an AI agent that gives every website visitor a live, personalized product demo.
---
## Product Overview
DemoAgent enables SaaS companies to embed a conversational AI demo agent on their websites. The agent:
- Navigates the **actual product** inside a live iframe (not screenshots or recordings)
- Answers visitor questions using product knowledge and documentation
- Executes scripted demo flows (click, type, scroll, highlight) in real-time
- Captures leads with configurable forms triggered by engagement milestones
- Uses a multi-agent architecture powered by Claude (Anthropic)
**Who it's for:** SaaS companies wanting to offer self-serve interactive product demos to website visitors without human involvement.
**How it works:**
1. Define your product structure using a PDP (Product Discovery Protocol) YAML file
2. Upload the PDP via API or dashboard
3. Embed the widget on your website with a single script tag
4. Visitors interact with an AI agent that showcases your product live
---
## Quick Start
### Step 1: Create an Account
```bash
curl -X POST https://api.iraa.ai/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"orgName": "My Company",
"email": "admin@mycompany.com",
"password": "securepassword123"
}'
```
Response includes your API keys:
```json
{
"organization": { "id": "uuid", "name": "My Company", "slug": "my-company" },
"keys": {
"publicKey": "pk_live_...",
"secretKey": "sk_live_...",
"testPublicKey": "pk_test_...",
"testSecretKey": "sk_test_..."
}
}
```
### Step 2: Upload a PDP Config
```bash
curl -X POST https://api.iraa.ai/api/v1/pdp \
-H "Authorization: Bearer sk_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "yaml": "pdp_version: \"1.0\"\nproduct:\n name: \"MyApp\"\n ..." }'
```
### Step 3: Embed the Widget
```html
```
---
## Widget Integration
### HTML Script Tag (Simplest)
```html
```
### Programmatic Initialization
```html
```
### React
```tsx
import { useEffect } from 'react';
export function DemoAgentWidget({ apiKey }: { apiKey: string }) {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdn.iraa.ai/widget.js';
script.async = true;
script.dataset.apiKey = apiKey;
document.body.appendChild(script);
return () => { document.body.removeChild(script); };
}, [apiKey]);
return null;
}
// Usage:
```
### Next.js
```tsx
import Script from 'next/script';
export default function Layout({ children }) {
return (
<>
{children}
>
);
}
```
### Vue
```vue
```
### Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `apiKey` | `string` | *required* | Your public API key (`pk_live_...` or `pk_test_...`) |
| `apiUrl` | `string` | `https://api.iraa.ai` | API base URL (override for self-hosted) |
| `wsUrl` | `string` | derived from apiUrl | WebSocket URL (override for custom setup) |
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Widget color theme |
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Widget position on page |
### Events
- `DemoAgentReady` — Dispatched on `window` when the widget script is loaded and ready for `init()`.
---
## Authentication
DemoAgent uses two types of API keys:
### Public Keys (`pk_live_*`, `pk_test_*`)
- Used in **client-side** widget code (visible to end users)
- Domain-restricted: only work from whitelisted origins (configured in dashboard)
- Can only access: session creation, chat, lead capture
- Pass via `Authorization: Bearer pk_live_...` header
### Secret Keys (`sk_live_*`, `sk_test_*`)
- Used in **server-side** code only (never expose to clients)
- Full access: PDP upload, key management, analytics
- Pass via `Authorization: Bearer sk_live_...` header
### Session Tokens
- JWT issued when a session is created via `POST /api/v1/session`
- Used to authenticate WebSocket connections and chat requests
- Contains: sessionId, orgId, productId
- Pass via `Authorization: Bearer ` header or `?token=` query param (WebSocket)
---
## REST API Reference
**Base URL:** `https://api.iraa.ai`
All request and response bodies are JSON unless otherwise noted.
### Auth Endpoints
#### POST /api/v1/auth/register
Create a new organization with initial user and API keys.
**Auth:** None
**Request body:**
```json
{
"orgName": "string (required)",
"email": "string (required)",
"password": "string (required, min 8 chars)"
}
```
**Response (200):**
```json
{
"organization": { "id": "uuid", "name": "string", "slug": "string" },
"keys": {
"publicKey": "pk_live_...",
"secretKey": "sk_live_...",
"testPublicKey": "pk_test_...",
"testSecretKey": "sk_test_..."
}
}
```
#### POST /api/v1/auth/login
Authenticate with email/password. Sets an httpOnly cookie (`da_session`).
**Auth:** None
**Request body:**
```json
{ "email": "string", "password": "string" }
```
**Response (200):**
```json
{
"user": { "id": "uuid", "email": "string", "name": "string", "role": "owner|admin|user" },
"organization": { "id": "uuid", "name": "string", "slug": "string", "plan": "string" }
}
```
#### POST /api/v1/auth/logout
Clear the auth cookie.
**Auth:** None (clears existing cookie)
**Response (200):** `{ "success": true }`
#### GET /api/v1/auth/me
Get the current authenticated user.
**Auth:** Cookie (`da_session`)
**Response (200):**
```json
{
"user": { "id": "uuid", "email": "string", "role": "string" },
"organization": { "id": "uuid", "name": "string", "slug": "string", "plan": "string" }
}
```
#### GET /api/v1/auth/keys
List API keys for the organization.
**Auth:** Secret key (`sk_live_*` or `sk_test_*`)
**Response (200):**
```json
{
"keys": [
{
"id": "uuid",
"type": "public|secret",
"keyPrefix": "pk_live_abc...",
"name": "string",
"allowedDomains": ["string"],
"lastUsedAt": "ISO8601|null",
"isActive": true,
"createdAt": "ISO8601"
}
]
}
```
#### POST /api/v1/auth/keys/rotate
Rotate an API key (deactivates old key, creates new one).
**Auth:** Secret key
**Request body:**
```json
{ "keyId": "uuid" }
```
**Response (200):**
```json
{
"key": "pk_live_new_key_value...",
"id": "uuid",
"prefix": "pk_live_new..."
}
```
---
### Session Endpoints
#### POST /api/v1/session
Create a new demo session. Returns a session token, greeting message, and branding config.
**Auth:** Public key (`pk_live_*` or `pk_test_*`)
**Request body:**
```json
{
"productId": "string|'default' (required)",
"visitorMeta": {
"referrer": "string (optional)",
"url": "string (optional)",
"language": "string (optional)"
}
}
```
**Response (200):**
```json
{
"sessionId": "uuid",
"token": "JWT session token",
"greeting": {
"text": "string",
"speech": "string (TTS text)",
"suggestions": ["string"]
},
"agent": { "name": "string", "tone": "string" },
"branding": {
"primaryColor": "#hex",
"widgetPosition": "bottom-right|bottom-left",
"agentName": "string",
"logoUrl": "string|null"
}
}
```
#### POST /api/v1/session/:sessionId/chat
Send a visitor message and get the agent's response.
**Auth:** Session token (JWT from session creation)
**Request body:**
```json
{ "message": "string (required)" }
```
**Response (200):**
```json
{
"text": "Agent's text response",
"speech": "TTS-optimized text (optional)",
"iframeCommands": [
{ "action": "navigate|click|type|highlight|scroll|wait", "selector": "string", "value": "string" }
],
"suggestions": ["Suggested reply 1", "Suggested reply 2"],
"agentId": "demo|qa|discovery|docs",
"usage": { "inputTokens": 0, "outputTokens": 0 }
}
```
#### POST /api/v1/session/:sessionId/lead
Capture lead information.
**Auth:** Session token
**Request body:**
```json
{
"email": "string (optional)",
"company": "string (optional)",
"name": "string (optional)",
"phone": "string (optional)"
}
```
**Response (200):**
```json
{ "leadId": "uuid", "captured": true }
```
#### POST /api/v1/session/:sessionId/end
End a demo session.
**Auth:** Session token
**Response (200):**
```json
{ "ended": true }
```
---
### PDP Endpoints
#### POST /api/v1/pdp
Upload and validate a PDP YAML file. Creates or updates the product and sets the config as active.
**Auth:** Secret key (`sk_live_*`)
**Request body:**
```json
{
"yaml": "string (PDP YAML content, required)",
"productId": "uuid (optional, auto-resolved by product name)"
}
```
**Response (200):**
```json
{
"id": "uuid (config ID)",
"productId": "uuid",
"version": 1,
"product": "Product Name",
"screens": 5,
"flows": 2,
"warnings": ["string"]
}
```
**Response (422) — Validation failed:**
```json
{
"error": "PDP validation failed",
"details": [{ "path": "string", "message": "string" }],
"warnings": ["string"]
}
```
#### POST /api/v1/pdp/validate
Validate a PDP YAML without storing it.
**Auth:** None
**Request body:**
```json
{ "yaml": "string (PDP YAML content)" }
```
**Response (200):**
```json
{
"valid": true,
"errors": [],
"warnings": [],
"summary": {
"product": "Product Name",
"screens": 5,
"flows": 2,
"demoAccounts": 1,
"faqs": 3
}
}
```
#### GET /api/v1/pdp/:id
Retrieve a PDP config by ID.
**Auth:** Secret key
**Response (200):** Full PDP config record (id, productId, version, config, isActive, etc.)
#### GET /api/v1/pdp/product/:productId/active
Get the active PDP config for a product.
**Auth:** None (public endpoint)
**Response (200):** Full PDP config record
---
### Discovery Endpoints
Discovery allows auto-generating a PDP by crawling your product's website.
#### POST /api/v1/discover/start
Start a discovery session. Returns a token URL to begin crawling.
**Auth:** Cookie (dashboard user)
**Request body:**
```json
{ "productId": "uuid" }
```
**Response (200):**
```json
{
"sessionId": "uuid",
"token": "JWT",
"discoveryUrl": "https://yourapp.com?da_discover=TOKEN",
"expiresAt": "ISO8601",
"product": { "id": "uuid", "name": "string", "baseUrl": "string" }
}
```
#### POST /api/v1/discover/validate-token
Validate a discovery token (called by the discovery script).
**Auth:** None (token-based)
**Request body:** `{ "token": "JWT" }`
**Response (200):** `{ "valid": true, "sessionId": "uuid", "productId": "uuid" }`
#### POST /api/v1/discover/snapshot
Submit a page snapshot during discovery.
**Auth:** Discovery token
**Request body:**
```json
{
"sessionId": "uuid",
"token": "JWT",
"snapshot": {
"urlPath": "/dashboard",
"pageTitle": "Dashboard",
"headings": ["string"],
"elements": [{}],
"navigation": [{}],
"forms": [{}],
"viewport": { "width": 1920, "height": 1080 }
}
}
```
**Response (200):** `{ "status": "saved", "id": "uuid", "uniquePages": 5 }`
#### POST /api/v1/discover/agent-chat
Chat with the discovery agent during exploration.
**Auth:** Discovery token
**Request body:**
```json
{
"sessionId": "uuid",
"token": "JWT",
"message": "This page is our main analytics dashboard",
"currentSnapshot": {}
}
```
**Response (200):** `{ "reply": "Agent's response text" }`
#### POST /api/v1/discover/finalize
End discovery and generate a PDP YAML.
**Auth:** Discovery token
**Request body:** `{ "sessionId": "uuid", "token": "JWT" }`
**Response (200):** `{ "status": "complete", "pdpYaml": "YAML string" }`
#### GET /api/v1/discover/status?productId=uuid
Check discovery progress.
**Auth:** Cookie (dashboard user)
**Response (200):**
```json
{
"hasSession": true,
"status": "active|finalizing|complete|expired",
"uniquePages": 8,
"totalElements": 142,
"generatedPdp": "YAML string|null",
"sessionId": "uuid"
}
```
---
### Dashboard Endpoints
All require cookie authentication (dashboard login).
- `GET /api/v1/dashboard/overview` — Product, session, and lead counts
- `GET /api/v1/dashboard/products` — List products for the organization
- `POST /api/v1/dashboard/products` — Create a new product
- `GET /api/v1/dashboard/keys` — List active API keys
- `POST /api/v1/dashboard/keys/:id/rotate` — Rotate an API key
- `GET /api/v1/dashboard/org` — Get organization details
- `PUT /api/v1/dashboard/org` — Update organization
- `POST /api/v1/dashboard/pdp` — Upload PDP via dashboard
- `GET /api/v1/dashboard/analytics` — Session analytics and recent sessions
---
## WebSocket API
Real-time communication for live demo sessions.
### Connection
```
wss://api.iraa.ai/ws/session/:sessionId?token=SESSION_JWT
```
The session JWT is obtained from `POST /api/v1/session`.
### Client to Server Messages
All messages are JSON with `type` and `payload` fields.
#### visitor_message
Send a chat message from the visitor.
```json
{
"type": "visitor_message",
"payload": { "text": "Show me the dashboard" }
}
```
#### heartbeat
Keep the connection alive.
```json
{
"type": "heartbeat",
"payload": {}
}
```
#### iframe_event
Report an event from the demo iframe (click, navigation, etc.).
```json
{
"type": "iframe_event",
"payload": { "event": "click", "selector": "#btn", "url": "/page" }
}
```
### Server to Client Messages
#### agent_response
Agent's reply with text, TTS audio reference, iframe commands, and suggested replies.
```json
{
"type": "agent_response",
"payload": {
"text": "Here's the dashboard! Let me show you the pipeline.",
"speech": "Here's the dashboard. Let me show you the pipeline.",
"iframeCommands": [
{ "action": "navigate", "url": "/dashboard" },
{ "action": "highlight", "selector": "[data-testid='pipeline-chart']" }
],
"suggestions": ["Create a deal", "Show contacts", "Tell me about pricing"],
"agentId": "demo"
}
}
```
#### typing_indicator
Agent is processing a response.
```json
{
"type": "typing_indicator",
"payload": { "agentId": "demo", "isTyping": true }
}
```
#### lead_form
Trigger the lead capture form in the widget.
```json
{
"type": "lead_form",
"payload": {
"fields": ["email", "company"],
"cta": { "text": "Start Free Trial", "url": "https://example.com/signup" }
}
}
```
#### error
Error occurred during processing.
```json
{
"type": "error",
"payload": { "code": "SESSION_EXPIRED", "message": "Session expired" }
}
```
Error codes: `AUTH_REQUIRED`, `INVALID_TOKEN`, `SESSION_NOT_FOUND`, `SESSION_EXPIRED`, `INVALID_JSON`, `EMPTY_MESSAGE`, `UNKNOWN_TYPE`, `PDP_NOT_FOUND`
#### pong
Response to heartbeat.
```json
{
"type": "pong",
"payload": { "ts": 1711000000000 }
}
```
---
## PDP Schema Reference
PDP (Product Discovery Protocol) is a YAML specification that defines everything about your product's demo: screens, elements, flows, agent personality, and configuration.
### Minimal Example
```yaml
pdp_version: "1.0"
product:
name: "MyApp"
base_url: "https://demo.myapp.com"
description: "Project management for small teams"
industry: "SaaS / Productivity"
target_personas:
- "Project Manager"
agent:
name: "Alex"
personality: "Friendly and knowledgeable product expert"
tone: "friendly"
llm:
provider: "anthropic"
model: "claude-sonnet-4-20250514"
temperature: 0.5
max_tokens: 1200
demo_accounts:
- id: "default_user"
role: "Admin"
credentials:
login_url: "https://demo.myapp.com/login"
login_method: "form"
fields:
"[name='email']": "enc:BASE64_ENCRYPTED_EMAIL"
"[name='password']": "enc:BASE64_ENCRYPTED_PASSWORD"
submit_selector: "button[type='submit']"
is_default: true
pool_size: 3
screens:
- id: "dashboard"
route: "/dashboard"
title: "Dashboard"
purpose: "Main overview with project stats"
elements:
- id: "create_project_btn"
type: "button"
selector: "[data-testid='create-project']"
label: "Create Project"
flows:
- id: "create_project"
title: "Create a New Project"
description: "Walk through creating a project"
trigger_phrases:
- "create a project"
- "new project"
role: "default_user"
steps:
- screen_id: "dashboard"
action: "click"
element_id: "create_project_btn"
narration: "Let me click the Create Project button."
knowledge:
product_faqs:
- question: "Is there a free plan?"
answer: "Yes, free for up to 5 users."
config:
lead_capture:
enabled: true
fields: ["email", "company"]
trigger: "after_3_steps"
branding:
primary_color: "#6366f1"
widget_position: "bottom-right"
allowed_domains:
- "myapp.com"
- "localhost"
```
### Complete Schema Reference
#### `pdp_version` (required)
- Type: `string`
- Value: `"1.0"`
#### `product` (required)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | Product name |
| `version` | string | no | Product version |
| `base_url` | string (URL) | yes | Demo environment URL |
| `description` | string | yes | Product description |
| `industry` | string | yes | Industry category |
| `target_personas` | string[] | yes | Target user roles |
#### `agent` (required)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | Agent display name |
| `personality` | string | yes | System prompt personality description |
| `tone` | `'friendly' \| 'professional' \| 'technical' \| 'casual'` | yes | Agent communication style |
| `voice` | object | no | TTS config: `{ provider, voice_id }` |
| `llm` | object | yes | LLM configuration |
**`agent.llm`:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `provider` | string | yes | `"anthropic"` |
| `model` | string | yes | Model ID (e.g., `"claude-sonnet-4-20250514"`) |
| `temperature` | number | yes | 0.0 - 1.0 |
| `max_tokens` | number | yes | Max response tokens |
#### `demo_accounts` (required, array)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Unique account identifier |
| `role` | string | yes | User role name |
| `credentials` | object | yes | Login configuration |
| `credentials.login_url` | string (URL) | yes | Login page URL |
| `credentials.login_method` | `'form' \| 'api' \| 'sso'` | yes | Auth method |
| `credentials.fields` | object | yes | CSS selector → encrypted value pairs |
| `credentials.submit_selector` | string | no | Submit button CSS selector |
| `permissions_summary` | string | no | Description of account permissions |
| `is_default` | boolean | no | Default account for new sessions |
| `pool_size` | number | no | Number of concurrent sessions this account supports |
| `reset_strategy` | `'api' \| 'db_snapshot' \| 'soft_delete' \| 'none'` | no | How to reset account state between sessions |
#### `screens` (required, array)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Unique screen identifier |
| `route` | string | yes | URL path (e.g., `"/dashboard"`) |
| `title` | string | yes | Human-readable screen name |
| `purpose` | string | yes | What this screen is for |
| `wait_for_selector` | string | no | CSS selector to wait for before screen is "ready" |
| `elements` | array | yes | Interactive elements on this screen |
| `tags` | string[] | no | Categorization tags |
| `related_screens` | string[] | no | IDs of related screens |
**`screens[].elements[]`:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Unique element identifier |
| `type` | string | yes | `button \| input \| select \| table \| chart \| tab \| link \| modal \| section \| textarea \| checkbox \| radio \| toggle` |
| `selector` | string | yes | CSS selector or `data-testid` |
| `label` | string | yes | Human-readable label |
| `description` | string | no | What this element does |
#### `example_values` (optional)
Provides realistic data for demo form fills and searches:
```yaml
example_values:
entities:
customers:
- name: "Horizon Technologies"
email: "sarah@horizontech.com"
deals:
- name: "Enterprise License"
value: "$48,000"
form_fills:
new_deal:
"[name='dealName']": "{{entities.deals[0].name}}"
search_queries:
- "Horizon Technologies"
```
#### `flows` (required, array)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | yes | Unique flow identifier |
| `title` | string | yes | Flow name |
| `description` | string | yes | What this flow demonstrates |
| `trigger_phrases` | string[] | yes | Phrases that activate this flow |
| `role` | string | yes | demo_account ID to use |
| `persona` | string | no | Target persona for this flow |
| `estimated_duration_seconds` | number | no | Expected duration |
| `steps` | array | yes | Ordered list of demo steps |
**`flows[].steps[]`:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `screen_id` | string | yes | Target screen ID |
| `action` | string | yes | `navigate \| click \| fill \| select \| highlight \| wait \| scroll` |
| `element_id` | string | no | Target element ID (for click, fill, select, highlight) |
| `value` | string | no | Value to fill or select |
| `narration` | string | yes | What the agent says during this step |
| `wait_ms` | number | no | Delay after this step (milliseconds) |
#### `knowledge` (required)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `product_faqs` | array | yes | `{ question, answer, category? }` |
| `competitive_positioning` | object | no | Competitor comparisons |
| `pricing_info` | object | no | `{ can_disclose, plans: [{ name, price, features }] }` |
| `docs_url` | string | no | External documentation URL |
| `custom_instructions` | string | no | Additional agent instructions |
#### `config` (required)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lead_capture.enabled` | boolean | yes | Enable lead capture |
| `lead_capture.fields` | string[] | yes | Fields to capture: `email`, `company`, `name`, `phone` |
| `lead_capture.trigger` | string | yes | `'after_N_steps'` or `'never'` |
| `cta` | object | no | `{ text, url }` — call-to-action button |
| `branding.primary_color` | string | yes | Hex color (e.g., `"#6366f1"`) |
| `branding.widget_position` | string | yes | `'bottom-right'` or `'bottom-left'` |
| `branding.agent_name` | string | no | Override agent display name |
| `branding.logo_url` | string | no | Custom logo URL |
| `idle_nudge_seconds` | number | no | Seconds before idle nudge |
| `session_timeout_seconds` | number | no | Session timeout |
| `allowed_domains` | string[] | yes | Domains where the widget can run |
| `gdpr_mode` | boolean | no | Enable GDPR-compliant mode |
---
## Multi-Agent Architecture
DemoAgent uses a graph-based multi-agent system:
```
Visitor Message
|
v
Router Agent (classifies intent)
|
+---> Demo Agent — navigates product, showcases features via iframe commands
+---> Q&A Agent — answers questions using product knowledge + RAG
+---> Discovery Agent — explores product structure with visitor
+---> Docs Agent — references technical documentation
```
**Intent types:** `demo_request`, `qa`, `discovery`, `handoff`, `chitchat`, `off_topic`
The Router Agent classifies each message and delegates to the appropriate specialized agent. Agents share conversation context and can hand off to each other mid-conversation.
---
## npm Packages
| Package | Description |
|---------|-------------|
| `@iraaai/widget` | Embeddable chat widget (Web Component with Shadow DOM) |
| `@iraaai/core` | Orchestrator, iframe controller, automation engine |
| `@iraaai/pdp-schema` | PDP YAML validation and parsing (`parsePDP(yaml)`) |
| `@iraaai/server` | Multi-agent graph, LLM adapters, RAG pipeline |
| `@iraaai/overlay` | Visual highlights, tooltips, spotlights, pulse animations |
| `@iraaai/cli` | CLI tools for PDP validation and credential encryption |
---
## Health & Meta Endpoints
- `GET /` — API info: `{ name: "DemoAgent API", version: "1.0.0" }`
- `GET /health` — Health check: `{ status: "ok" }`
---
## Full PDP Example
See the complete example PDP for a CRM product:
https://iraa.ai/docs/pdp#full-example
---
## Links
- **Website:** https://iraa.ai
- **Dashboard:** https://app.iraa.ai
- **API Base URL:** https://api.iraa.ai
- **Widget CDN:** https://cdn.iraa.ai/widget.js
- **Contact:** hello@iraa.ai