diff --git a/backend/app/agent_message_service.py b/backend/app/agent_message_service.py index 287f804..7e0b782 100644 --- a/backend/app/agent_message_service.py +++ b/backend/app/agent_message_service.py @@ -24,6 +24,7 @@ class SendAgentMessagePayload(BaseModel): customer_name: str = Field(min_length=1, max_length=120) report_group: str = Field(min_length=3, max_length=50) report_id: str | None = Field(default=None, max_length=120) + report_url: str | None = Field(default=None, max_length=500) def serialize_agent_template(template: AgentMessageTemplate, *, subscribed: bool = False) -> dict: @@ -97,31 +98,46 @@ def _validate_report_id(report_id: str) -> str: return cleaned +def _build_report_url( + template: AgentMessageTemplate, + report_group: str, + report_id: str, + override: str | None = None, +) -> str: + if override: + return override.strip() + base = (template.button_url_base or "https://urmiai.com/reports/").rstrip("/") + return f"{base}/{report_group}/{report_id}" + + def _build_template_components( template: AgentMessageTemplate, customer_name: str, - report_group: str, - report_id: str, + report_url: str, ) -> list[dict]: + """Build Meta template components using positional variables. + + agent_run_completion uses: + body {{1}} -> customer name + button {{1}} -> report URL path suffix OR full URL depending on Meta template config. + We pass the full hosted report URL for the button so the View Report link resolves correctly. + """ components: list[dict] = [ { "type": "body", - "parameters": [ - { - "type": "text", - "parameter_name": "customer_name", - "text": customer_name.strip(), - } - ], + "parameters": [{"type": "text", "text": customer_name.strip()}], } ] if template.button_label and template.button_url_base: + # Meta URL button: pass path suffix relative to button_url_base (e.g. desi-clans-test/2026-10-03) + base = template.button_url_base.rstrip("/") + button_param = report_url[len(base) + 1 :] if report_url.startswith(f"{base}/") else report_url components.append( { "type": "button", "sub_type": "url", "index": "0", - "parameters": [{"type": "text", "text": f"{report_group}/{report_id}"}], + "parameters": [{"type": "text", "text": button_param}], } ) return components @@ -148,7 +164,8 @@ async def send_agent_template_message( report_group = _validate_report_group(payload.report_group) report_id = _validate_report_id(payload.report_id or f"run-{secrets.token_hex(3)}") - components = _build_template_components(template, payload.customer_name, report_group, report_id) + report_url = _build_report_url(template, report_group, report_id, payload.report_url) + components = _build_template_components(template, payload.customer_name, report_url) client = _whatsapp_client(db) try: @@ -170,6 +187,7 @@ async def send_agent_template_message( "customer_name": payload.customer_name.strip(), "report_group": report_group, "report_id": report_id, + "report_url": report_url, }, wa_message_id=wa_message_id, status="sent", @@ -179,11 +197,6 @@ async def send_agent_template_message( db.commit() db.refresh(log) - report_url = ( - f"{template.button_url_base.rstrip('/')}/{report_group}/{report_id}" - if template.button_url_base - else None - ) return { "success": True, "template": template.slug, @@ -279,22 +292,24 @@ def build_agent_message_docs(template: AgentMessageTemplate) -> dict: "customer_name": "Recipient name inserted into the template body", "report_group": "Urmiport report group slug used in the hosted report URL", "report_id": "Optional report postfix; auto-generated when omitted", + "report_url": "Optional full report URL override for the WhatsApp button (auto-built when omitted)", }, "example": { "template": template.slug, "to": "919149154337", "customer_name": "Suyash", - "report_group": "acme-agent-runs", - "report_id": "run-abc123", + "report_group": "desi-clans-test", + "report_id": "2026-10-03", + "report_url": "https://urmiai.com/reports/desi-clans-test/2026-10-03", }, "response_example": { "success": True, "template": template.slug, "to": "919149154337", "customer_name": "Suyash", - "report_group": "acme-agent-runs", - "report_id": "run-abc123", - "report_url": f"{template.button_url_base or ''}acme-agent-runs/run-abc123", + "report_group": "desi-clans-test", + "report_id": "2026-10-03", + "report_url": "https://urmiai.com/reports/desi-clans-test/2026-10-03", "wa_message_id": "wamid.HBg...", "dispatch_id": "00000000-0000-0000-0000-000000000001", }, @@ -304,5 +319,6 @@ def build_agent_message_docs(template: AgentMessageTemplate) -> dict: "Messages are sent from the Urmi AI WhatsApp business number on your behalf.", "Create the report group in Console → Agent → Urmiport before sending.", "If report_id is omitted, Urmi generates a unique postfix automatically.", + "The WhatsApp button uses positional template variables: body {{1}} = customer_name, button suffix = report_group/report_id.", ], } diff --git a/backend/app/agent_templates_seed.py b/backend/app/agent_templates_seed.py index 2498f54..fed664b 100644 --- a/backend/app/agent_templates_seed.py +++ b/backend/app/agent_templates_seed.py @@ -22,25 +22,18 @@ DEFAULT_AGENT_TEMPLATES = [ "category": "utility", "parameters": [ { - "name": "customer_name", + "name": "{{1}}", "type": "string", "required": True, - "description": "Recipient display name shown in the greeting", + "description": "Positional body variable — recipient display name", "example": "Suyash", }, { - "name": "report_group", + "name": "{{2}}", "type": "string", "required": True, - "description": "Urmiport report group slug used in the hosted report URL", - "example": "acme-agent-runs", - }, - { - "name": "report_id", - "type": "string", - "required": True, - "description": "Report postfix appended after the report group in the View Report button URL", - "example": "run-abc123", + "description": "Positional button variable — report path suffix (report_group/report_id)", + "example": "desi-clans-test/2026-10-03", }, ], "button_label": "View Report", diff --git a/backend/app/routes_agent_messages.py b/backend/app/routes_agent_messages.py index c433b8d..b9f20a8 100644 --- a/backend/app/routes_agent_messages.py +++ b/backend/app/routes_agent_messages.py @@ -55,7 +55,13 @@ def _explorer_doc_for_template(template: AgentMessageTemplate) -> dict: "type": "string", "required": False, "description": "Report postfix; auto-generated when omitted", - "example": "run-abc123", + "example": "2026-10-03", + }, + "report_url": { + "type": "string", + "required": False, + "description": "Full hosted report URL for the WhatsApp button; auto-built when omitted", + "example": "https://urmiai.com/reports/desi-clans-test/2026-10-03", }, }, "responses": { @@ -66,9 +72,9 @@ def _explorer_doc_for_template(template: AgentMessageTemplate) -> dict: "template": template.slug, "to": "919149154337", "customer_name": "Suyash", - "report_group": "acme-agent-runs", - "report_id": "run-abc123", - "report_url": f"{template.button_url_base or ''}acme-agent-runs/run-abc123", + "report_group": "desi-clans-test", + "report_id": "2026-10-03", + "report_url": "https://urmiai.com/reports/desi-clans-test/2026-10-03", "wa_message_id": "wamid.HBg...", "dispatch_id": "00000000-0000-0000-0000-000000000001", }, diff --git a/docs/URMIPORT_AND_WHATSAPP_APIS.md b/docs/URMIPORT_AND_WHATSAPP_APIS.md new file mode 100644 index 0000000..891d3f2 --- /dev/null +++ b/docs/URMIPORT_AND_WHATSAPP_APIS.md @@ -0,0 +1,365 @@ +# Urmiport & Agent WhatsApp APIs + +This document describes the two APIs used together to **host an HTML report** and **notify a user on WhatsApp** with a link to that report. + +| API | Purpose | +|-----|---------| +| **Urmiport — Publish report** | Create or update a hosted HTML report | +| **Agent messages — Send WhatsApp** | Send an approved WhatsApp template with a dynamic View Report button | + +**Base URL:** `https://urmiai.com` + +**Authentication:** Urmi API key in every request + +```http +Authorization: Bearer urmi_live_ +``` + +Alternate header: `X-Api-Key: urmi_live_` + +Create API keys in the Urmi console: **Develop → API Keys**. + +--- + +## End-to-end flow + +```text +1. Console → Agent → Urmiport + Create a report group (e.g. desi-clans-test) + +2. Console → Agent → UrmiAI + Subscribe to the agent_run_completion template + +3. POST /api/v1/urmiport/reports + Publish HTML for report_group + report_id + +4. POST /api/v1/agent/messages/send + Send WhatsApp message; button links to the hosted report + +5. User opens + https://urmiai.com/reports/{report_group}/{report_id} +``` + +Typical order: **publish the report first**, then **send the WhatsApp message** so the link is live when the user taps View Report. + +--- + +## Prerequisites (console setup) + +### 1. Create a report group + +**Console → Agent → Urmiport → New report group** + +| Setting | Description | +|---------|-------------| +| **Report group** | URL slug, 3–50 chars, lowercase letters, numbers, hyphens (e.g. `desi-clans-test`) | +| **Default access** | `public` or `password` | +| **Report ID mode** | `auto` — Urmi generates `report_id` when omitted; `manual` — you must send `report_id` in the API | + +Hosted URL pattern: + +```text +https://urmiai.com/reports/{report_group}/{report_id} +``` + +### 2. Subscribe to the WhatsApp template + +**Console → Agent → UrmiAI** + +Subscribe to **`agent_run_completion`** (display name: *Agent Run Success*). + +Messages are sent from the Urmi AI WhatsApp Business number on your behalf. + +--- + +## API 1 — Publish Urmiport HTML report + +Create or update a hosted HTML report. The public page renders **only your HTML** — no Urmi branding or chrome. + +### Endpoint + +```http +POST /api/v1/urmiport/reports +Content-Type: application/json +Authorization: Bearer urmi_live_ +``` + +### Request body + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `report_group` | Yes | string | Report group slug created in Urmiport (e.g. `desi-clans-test`) | +| `body` | Yes | string | Full HTML document or fragment (email-style HTML is supported) | +| `report_id` | Conditional | string | Report postfix. **Required** when the group uses manual ID mode; optional for auto groups | +| `access` | No | string | `public` (default) or `password` | +| `password` | Conditional | string | Required when `access` is `password`, unless the group already has a default password | + +### Validation rules + +- **report_group:** 3–50 chars, lowercase `a-z`, `0-9`, hyphens; must already exist in your account +- **report_id:** letters, numbers, dots, underscores, hyphens; max 120 chars +- **Upsert:** same `report_group` + `report_id` updates the existing HTML body + +### Example — public report (manual report ID) + +```bash +curl -X POST 'https://urmiai.com/api/v1/urmiport/reports' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "report_group": "desi-clans-test", + "report_id": "desi-clans-demo-v1", + "access": "public", + "body": "

Agent run complete

All checks passed.

" + }' +``` + +### Example — password-protected report + +```bash +curl -X POST 'https://urmiai.com/api/v1/urmiport/reports' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "report_group": "desi-clans-test", + "report_id": "run-confidential-001", + "access": "password", + "password": "my-secret-pass", + "body": "

Confidential report

" + }' +``` + +### Example — auto-generated report ID + +When the report group uses **auto** ID mode, omit `report_id`: + +```bash +curl -X POST 'https://urmiai.com/api/v1/urmiport/reports' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "report_group": "acme-agent-runs", + "access": "public", + "body": "

Run complete

" + }' +``` + +Urmi returns a generated `report_id` such as `run-a1b2c3`. + +### Success response `200 OK` + +```json +{ + "success": true, + "id": "11926afa-c648-48f2-909e-67434f878232", + "report_group": "desi-clans-test", + "report_id": "desi-clans-demo-v1", + "access": "public", + "password_protected": false, + "url": "https://urmiai.com/reports/desi-clans-test/desi-clans-demo-v1", + "created_at": "2026-10-02T17:13:32.468390Z", + "updated_at": "2026-10-02T17:13:32.468390Z" +} +``` + +### Error responses + +| Status | Meaning | +|--------|---------| +| `401` | Missing or invalid API key | +| `404` | Report group not found — create it in Console → Agent → Urmiport | +| `422` | Validation error (invalid slug, missing `report_id` for manual groups, missing password, etc.) | + +### Public viewing + +| Access | Behaviour | +|--------|-----------| +| **Public** | `GET https://urmiai.com/reports/{report_group}/{report_id}` renders HTML directly | +| **Password** | User sees a password gate; after unlock, only your HTML is shown | + +Internal metadata endpoint (no auth): + +```http +GET /api/v1/urmiport/view/{report_group}/{report_id} +POST /api/v1/urmiport/view/{report_group}/{report_id}/unlock +``` + +### HTML capabilities + +Reports support plain **HTML, CSS, and JavaScript** — including charts, tables, branding, and client-side animations. Complex dashboards (canvas charts, live counters, responsive layouts) are supported. + +### Machine-readable docs + +```http +GET /api/v1/urmiport/docs +GET /api/v1/urmiport/reports/docs +``` + +Also available in the console: **Agent → Urmiport** and **Develop → API Explorer**. + +--- + +## API 2 — Send WhatsApp report notification + +Send an approved agent WhatsApp template. The template includes a **View Report** button whose URL is built from `report_group` and `report_id`. + +### Endpoint + +```http +POST /api/v1/agent/messages/send +Content-Type: application/json +Authorization: Bearer urmi_live_ +``` + +### Request body + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `template` | Yes | string | Subscribed template slug. Use `agent_run_completion` | +| `to` | Yes | string | Recipient WhatsApp number with country code, no `+` (e.g. `919149154337`) | +| `customer_name` | Yes | string | Name inserted into the template body greeting | +| `report_group` | Yes | string | Urmiport report group slug (same as in publish API) | +| `report_id` | No | string | Report postfix. If omitted, Urmi auto-generates one (e.g. `run-a1b2c3`) | +| `report_url` | No | string | Full hosted report URL override. Auto-built as `https://urmiai.com/reports/{report_group}/{report_id}` when omitted | + +### Template details — `agent_run_completion` + +| Property | Value | +|----------|-------| +| **Slug** | `agent_run_completion` | +| **Meta template name** | `agent_run_completion` | +| **Header** | Agent Run Complete | +| **Body** | Hello {{1}}, your agent run is complete… | +| **Button** | View Report → `https://urmiai.com/reports/{{1}}` | + +The Meta template uses **positional** variables (not named): + +| Variable | Maps to API field | Example | +|----------|-------------------|---------| +| `{{1}}` (body) | `customer_name` | `Suyash` | +| `{{1}}` (button) | `report_group`/`report_id` path suffix | `desi-clans-test/2026-10-03` | + +Urmi sends the button suffix `desi-clans-test/2026-10-03` so the final link is: + +`https://urmiai.com/reports/desi-clans-test/2026-10-03` + +> **Common bug:** If body parameters are sent as named (`parameter_name`) instead of positional, Meta leaves `{{1}}` literal in the URL, producing broken links like `https://urmiai.com/reports/%7B%7B1%7D%7Ddesi-clans-test/2026-10-03`. + +### Example + +```bash +curl -X POST 'https://urmiai.com/api/v1/agent/messages/send' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "template": "agent_run_completion", + "to": "919149154337", + "customer_name": "Suyash", + "report_group": "desi-clans-test", + "report_id": "desi-clans-demo-v1" + }' +``` + +### Success response `200 OK` + +```json +{ + "success": true, + "template": "agent_run_completion", + "to": "919149154337", + "customer_name": "Suyash", + "report_group": "desi-clans-test", + "report_id": "2026-10-03", + "report_url": "https://urmiai.com/reports/desi-clans-test/2026-10-03", + "wa_message_id": "wamid.HBg...", + "dispatch_id": "00000000-0000-0000-0000-000000000001" +} +``` + +### Error responses + +| Status | Meaning | +|--------|---------| +| `401` | Missing or invalid API key | +| `403` | Template not subscribed — subscribe in Console → Agent → UrmiAI | +| `422` | Invalid phone number, `report_group`, or `report_id` format | +| `502` | Meta WhatsApp API error (template mismatch, WABA issue, etc.) | + +### Machine-readable docs + +```http +GET /api/v1/agent/messages/docs/send +``` + +Per-template docs (console session required): + +```http +GET /api/v1/agent-templates/agent_run_completion/docs +``` + +Also available in the console: **Agent → UrmiAI → API docs** and **Develop → API Explorer**. + +--- + +## Complete integration example + +Publish a report, then notify the user on WhatsApp with the same `report_group` and `report_id`. + +### Step 1 — Publish report + +```bash +curl -X POST 'https://urmiai.com/api/v1/urmiport/reports' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "report_group": "desi-clans-test", + "report_id": "desi-clans-demo-v1", + "access": "public", + "body": "

Desi Clans — Run complete

All systems operational.

" + }' +``` + +Report URL: `https://urmiai.com/reports/desi-clans-test/desi-clans-demo-v1` + +### Step 2 — Send WhatsApp notification + +```bash +curl -X POST 'https://urmiai.com/api/v1/agent/messages/send' \ + -H 'Authorization: Bearer urmi_live_' \ + -H 'Content-Type: application/json' \ + -d '{ + "template": "agent_run_completion", + "to": "919149154337", + "customer_name": "Suyash", + "report_group": "desi-clans-test", + "report_id": "desi-clans-demo-v1" + }' +``` + +The recipient receives a WhatsApp message with a **View Report** button pointing to the hosted report. + +--- + +## Quick reference + +| Item | Value | +|------|-------| +| Base URL | `https://urmiai.com` | +| Publish report | `POST /api/v1/urmiport/reports` | +| Send WhatsApp | `POST /api/v1/agent/messages/send` | +| Report URL | `https://urmiai.com/reports/{report_group}/{report_id}` | +| Template slug | `agent_run_completion` | +| Auth header | `Authorization: Bearer urmi_live_` | + +--- + +## Console links + +| Task | Location | +|------|----------| +| Create report groups | Console → Agent → Urmiport | +| Subscribe WhatsApp template | Console → Agent → UrmiAI | +| Create API keys | Console → Develop → API Keys | +| Try APIs interactively | Console → Develop → API Explorer | + +urmi_live_a9d92e7be1584e24aa361cb09075794a_FWsjx04IW3Z2Enqx4Lg0EybTqCYsa0Dk diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 69041a6..6fca235 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -523,7 +523,14 @@ export const agentMessagesApi = { docs: () => jsonRequest('/api/v1/agent/messages/docs/send'), send: ( apiKey: string, - payload: { template: string; to: string; customer_name: string; report_group: string; report_id?: string }, + payload: { + template: string + to: string + customer_name: string + report_group: string + report_id?: string + report_url?: string + }, ) => fetch(`${API_BASE_URL}/api/v1/agent/messages/send`, { method: 'POST',