# Data Structure Documentation Source: https://docs.lovi.ai/en/api-reference/api_data_en Official documentation for extracting raw data from the database # Official documentation for extracting raw data from the database This document aims to provide access and a description of the main data structures available to the company to facilitate proper integration, management, and analysis of information related to interactions and participating agents. In addition to describing each table structure, it includes recommendations and warnings regarding the responsible handling of personal data that may be found in the records, highlighting the importance of complying with current privacy and information security regulations. We suggest carefully reviewing each field definition and the notes on handling personal data before starting any integration or analysis process to ensure the safe and proper use of the provided database. The data presented is raw and unprocessed. ## Accessing the Data in ClickHouse via HTTP and BI Tools This section explains how to connect to the Botslovers ClickHouse database, either through direct HTTP access (for example using `curl`) or via graphical tools and Business Intelligence tools like DBeaver, Power BI, or Tableau. ### Access via HTTP (example using curl) You can access the data by executing SQL queries directly against the ClickHouse database via its HTTP interface. **Basic Example:** To retrieve the first 10 records from the `messages_` table: ```bash theme={null} curl -u user:'password' 'https://public-clickhouse.botslovers.com/?query=SELECT%20*%20FROM%20messages_company%20LIMIT%2010' ``` Replace `user` and `password` with your personal credentials. The `query` parameter must be URL-encoded. **Response Formats:** By default, the response is in TabSeparated format. You can request other formats by adding `FORMAT` at the end of the query: **JSON Example:** ```bash theme={null} curl -u user:'password' "https://public-clickhouse.botslovers.com/?query=SELECT%20*%20FROM%20messages_company%20LIMIT%2010%20FORMAT%20JSON" ``` **CSV Example:** ```bash theme={null} curl -u user:'password' "https://public-clickhouse.botslovers.com/?query=SELECT%20*%20FROM%20messages_company%20LIMIT%2010%20FORMAT%20CSV" ``` ### Access from BI Tools or Database Clients (DBeaver, Power BI, Tableau, etc.) ClickHouse supports JDBC and ODBC connectors, allowing you to use graphical tools to explore and analyze the data. #### Access via DBeaver * Open DBeaver and create a new connection. * Select ClickHouse as the database type. * Choose HTTP as the connection type. * Fill in the following fields: * Host: `public-clickhouse.botslovers.com` * Port: `443` * User: your username * Password: your password * SSL: enabled (make sure the box is checked) * Test the connection and save. #### Access from BI Tools (Power BI, Tableau, etc.) **Power BI** * Download and install the official ClickHouse ODBC driver. * Configure an ODBC DSN pointing to: * Host: `public-clickhouse.botslovers.com` * Port: `443` (or `8443` depending on configuration, check with your administrator) * SSL Mode: enabled * Username/Password: your credentials * In Power BI, create a new ODBC data source and select the configured DSN. **Tableau** * You can use the ClickHouse ODBC driver as in Power BI. * Alternatively, use the native ClickHouse connector. ## Security and Best Practices * Your credentials are personal and must not be shared. * Consult your administrator if you have questions about usage limits or data structure. * We recommend avoiding queries that extract large volumes of data to prevent potential service performance issues. ## Tables ### `messages` Acts as the central repository where each individual message generated in a conversation is stored. This document details its fields, purpose, and specific recommendations for managing sensitive data securely. ### `agent_activity` Tables that concentrate relevant information about agents — human or automated — participating in conversations. They facilitate associating events and metrics to each agent for individual activity analysis, key indicator calculations (e.g., response times, session closures, agent availability), and action traceability across conversations, even when transfers occur. ## Table Descriptions ### `messages` (Table: `messages_`) Stores every message exchanged during conversations. | Field | Type | Description | | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `sender_id` | String | Unique sender identifier linking each message to a specific entity. | | `sender_type` | String | Message origin: `"user"`, `"bot"`, `"system"`, or `"human"`. | | `id` | String | Unique key of each message, ensuring duplicate detection. | | `created_at` | DateTime | Timestamp in UTC for when the message was generated. | | `channel` | String | Channel through which the message was sent: `"whatsapp"` or `"web"` (widget). | | `session` | String | Conversation identifier grouping multiple messages. | | `direction` | String | Message direction: `"inbound"` or `"outbound"`. | | `type` | String | Communicative type: `"inbound"` (received) or `"outbound"` (sent). | | `text` | String | Full free-text content of the message. May include text, emojis, emails, IDs, etc.
**Note:** May be `null` for images or documents. | | `language` | String | Language code (e.g., `"es"`, `"en"`).
**Note:** May be `null` if undetected. | ### `agent_activity` (Table: `chat_agent_log_`) Records events occurring during agents' interaction with chats. | Field | Type | Description | | ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | String | Unique event identifier. | | `chat_id` | String | Associated chat identifier. | | `agent_id` | String | Unique agent identifier involved in the event. | | `joined_at` | DateTime | UTC timestamp when the agent joined the chat. | | `left_at` | Nullable(DateTime) | UTC timestamp when the agent left the chat, or `null` if still active. | | `event` | String | Type of event. Possible values: `bot_assigned`, `agent_timeout`, `user_timeout`, `agent_joined`, `chat_escalated`, `manual_close`, `resolved`, `assigned_to_human`, `transferred`. | | `response_rating` | Nullable(Int16) | Rating given for the interaction, if any. | | `created_at` | DateTime | Record creation timestamp. | | `updated_at` | DateTime | Record last update timestamp. | | `deleted_at` | Nullable(DateTime) | Logical deletion timestamp, if applicable. | ### `agent_status_logs` (Table: `agent_status_logs_`) Tracks the history of agent status changes over time, such as transitioning from "online" to "break". | Field | Type | Description | | ------------------- | --------------- | -------------------------------------------------------------------------------------------- | | `id` | String | Unique status change identifier. | | `agent_id` | String | Unique agent identifier. | | `old_status` | String | Previous agent status: `training`, `offline`, `break`, `ending_shift`, `inactive`, `online`. | | `new_status` | String | New agent status: `training`, `offline`, `break`, `ending_shift`, `inactive`, `online`. | | `status_changed_at` | DateTime | UTC timestamp when the status changed. | | `duration_seconds` | Nullable(Int64) | Duration in seconds of the previous status, or `null` if not applicable. | | `created_at` | DateTime | Record creation timestamp. | ## Handling of Personal Data The `messages_`table, especially the `text` column, retains the full content of customer interactions, potentially containing personal data such as names, email addresses, ID numbers, or other details about the products or services offered by your company. These records have not been anonymized or pseudonymized to preserve the full context of each conversation and ensure analysis accuracy. It is **essential** to process this data according to applicable regulations, always maintaining confidentiality, security, and the rights of data subjects. # WhatsApp Notifications API Source: https://docs.lovi.ai/en/api-reference/api_push_whatsapp_en Official documentation for using Lovi's API with WhatsApp via Postman ## Introduction This section provides the official documentation for using Lovi's API with WhatsApp via Postman. It includes detailed instructions on how to set up and test API requests for WhatsApp integration, ensuring smooth communication through the platform. Authentication is performed using tokens that enable basic authentication for the API services. For more details on how to authenticate, please refer to the [Authentication](./authentication) page. The Lovi API supports WhatsApp notifications with multimedia content, dynamic placeholders, scheduled delivery, and conversation flow integration. ### Key Features: * WhatsApp notifications with multimedia support * Dynamic content personalization with placeholders * Scheduled message delivery with timezone support * Conversation flow integration * Two data structure formats (nested and flat) * Campaign status via `inquiry_set_id` and `GET /notify/status` (same endpoint for voice campaigns) *** ## 📣 Send WhatsApp Notification To send a notification via the Lovi API, make a POST request to the endpoint with the necessary parameters and authentication. > **Method**: POST **Format**: JSON ### Endpoint ``` POST https://cloud.lovi.ai/functions/v1/notify?access_key={YOUR_ACCESS_KEY} ``` ### Query Parameters | Parameter | Required | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `access_key` | Yes | Your unique API access key. | | `unflatten` | No | If set to `true`, the body must contain flat variables (no nested objects). If set to `false` or omitted, the body can contain nested objects. | **Example URLs:** ``` POST https://cloud.lovi.ai/functions/v1/notify?access_key=your-api-key POST https://cloud.lovi.ai/functions/v1/notify?access_key=your-api-key&unflatten=true ``` ### Headers | Key | Value | Required | Description | | ------------ | ---------------- | -------- | -------------------------------------------------- | | Content-Type | application/json | Yes | Indicates that the request body is in JSON format. | **Note**: Authentication is handled via the `access_key` parameter in the URL, not through headers. *** ## 📋 Request Parameters The API supports two data structure formats controlled by the `unflatten` parameter. ### Required Parameters | Parameter | Type | Description | Example | | ----------------------- | --------------- | ---------------------------------------------------------------------------- | --------------------------------------------- | | `contact` OR `contacts` | Object OR Array | Single number contact (object) OR Multiple numbers contacts (array, max 100) | See below | | `language_template` | String | Language code for the template | `"es_ES"`, `"en_US"`, `"es"`, `"es"` | | `name_template` | String | Name of the approved template | `"welcome_user"` | | `recipient_id` | String | Sender's ID number or phone number | `"34666033135"` | | `notification_type` | String | Type for analytics | `"marketing"`, `"transactional"`, `"utility"` | | `campaign_name` | String | Campaign identifier name | `"Black Friday 2024"` | **Important**: You must use either `contact` (for single recipient) OR `contacts` (for multiple recipients), but NOT both. ### Optional Parameters | Parameter | Type | Description | When to Use | Example | | ------------------- | -------- | ----------------------------------- | --------------------------------------------------- | ----------------------- | | `contact.name` | String | Contact's name for personalization | For personalized messages | `"Juan Pérez"` | | `contact.email` | String | Contact's email | For additional contact data | `"juan@email.com"` | | `name_event` | String | Event to trigger conversation flows | When you want to start a specific conversation flow | `"intent-general-push"` | | `datetime_sending` | DateTime | Scheduled delivery time (ISO 8601) | When you want to schedule the message for later | `"2024-12-25T10:30:00"` | | `timezone` | String | Timezone for scheduled delivery | When using `datetime_sending` | `"Europe/Madrid"` | | `components_push.*` | Mixed | Template components (text, media) | When template requires dynamic content | See components section | *** ## 👥 Single vs Multiple Recipients ### Using `contact` - Send to One Person Use `contact` when you want to send a notification to **one recipient**. **Structure**: ```json theme={null} { "contact": { "number": "34666033135", "name": "John", "email": "john@example.com" } } ``` * `contact` is an **object** (not a list) * Required field: `number` * Optional fields: `name`, `email`, and any custom fields *** ### Using `contacts` - Send to Multiple People (Bulk Sending) Use `contacts` when you want to send the same notification to **multiple recipients** at once. **Structure**: ```json theme={null} { "contacts": [ { "number": "34666033135", "name": "John" }, { "number": "34777044246", "name": "Sarah" }, { "number": "34888055357", "name": "Michael" } ] } ``` * `contacts` is a **list/array** (not a single object) * **Maximum**: 100 contacts per request * Each contact in the list must have a `number` * Optional fields: `name`, `email`, and any custom fields **Important Restrictions**: * ⚠️ **Cannot use `unflatten=true` with `contacts`** - Bulk sending only works with nested structure * ⚠️ **Cannot use both `contact` and `contacts`** in the same request - choose one * ⚠️ **`contacts` list cannot be empty** - must have at least 1 contact *** ## 🔄 Data Structure Formats The API supports two formats based on the `unflatten` parameter: ### Nested Structure (unflatten=false or omitted) When `unflatten=false` or not specified, use nested objects: ```json theme={null} { "contact": { "number": "34666033135", "name": "Juan Pérez", "email": "juan@email.com" }, "components_push": { "header_text_0": "Welcome Message", "body_text_0": "Hello {{name}}", "body_text_1": "Your order is ready!", "footer_text_0": "Support Team" }, "language_template": "es_ES", "name_template": "order_ready", "recipient_id": "34666033135", "notification_type": "transactional", "campaign_name": "Order Notifications" } ``` ### Flat Structure (unflatten=true) When `unflatten=true`, all nested objects must be flattened using dot notation: ```json theme={null} { "contact.number": "34666033135", "contact.name": "Juan Pérez", "contact.email": "juan@email.com", "components_push.header_text_0": "Welcome Message", "components_push.body_text_0": "Hello {{name}}", "components_push.body_text_1": "Your order is ready!", "components_push.footer_text_0": "Support Team", "language_template": "es_ES", "name_template": "order_ready", "recipient_id": "34666033135", "notification_type": "transactional", "campaign_name": "Order Notifications" } ``` ### When to Use Each Format * **Nested Structure (`unflatten=false`)**: Recommended for better readability and when your system supports nested objects * **Flat Structure (`unflatten=true`)**: Use when your system doesn't support nested objects or requires flat data structure *** ## 🎨 Components & Multimedia **IMPORTANT**: Components you can send dynamically are **ONLY those returned by the template components endpoint**. The structure varies depending on whether the template has variables or media. ### Component Naming Rules * **Header media**: `header_image`, `header_video`, `header_document` (NO suffix number) * **Body variables**: `body_text_0`, `body_text_1`, `body_text_2`, etc. (with index number for each `{"{1}"}`, `{"{2}"}`, `{"{3}"}` placeholder) * **Footer and Buttons**: Are STATIC in the template definition and CANNOT be sent dynamically ### How to Know Which Components to Send 1. First, call the template components endpoint: ``` GET /notify/template/components?access_key={key}&template={template_id} ``` 2. The API returns only the components you need to provide: ```json theme={null} { "status": "OK", "components": ["header_image", "body_text_0", "body_text_1"] } ``` 3. Send ONLY those components in your notification request ### Component Types by Position #### Header Components (Media Only) **IMPORTANT**: Only ONE header component per template. Header text is STATIC in the template. | Type | Component Name | Description | Example Value | Specifications | | ---------- | ----------------- | ------------------- | --------------------------------- | ------------------------------ | | `image` | `header_image` | Header image URL | `"https://example.com/image.jpg"` | JPG, PNG, GIF - Max 5MB | | `video` | `header_video` | Header video URL | `"https://example.com/video.mp4"` | MP4, AVI, MOV - Max 16MB, 30s | | `document` | `header_document` | Header document URL | `"https://example.com/doc.pdf"` | PDF, DOC, XLS, PPT - Max 100MB | **Note**: `header_text` is NOT a dynamic component. Header text is defined in the template and cannot be changed. #### Body Components (Variables Only) Body text with placeholders requires variables in order: `{"{1}"}`, `{"{2}"}`, `{"{3}"}`, etc. | Component Name | Description | Maps to Template Placeholder | | -------------- | ----------------------- | ---------------------------- | | `body_text_0` | First variable in body | `{"{1}"}` | | `body_text_1` | Second variable in body | `{"{2}"}` | | `body_text_2` | Third variable in body | `{"{3}"}` | | `body_text_N` | N-th variable in body | `{"{N}"}` | **Example Template**: "Hello `{"{1}"}`, your course `{"{2}"}` is ready" * `body_text_0`: Value for `{"{1}"}` (e.g., "Maria") * `body_text_1`: Value for `{"{2}"}` (e.g., "JavaScript") #### Footer Components ⚠️ **Footer is STATIC** - defined in the template and cannot be modified per message. #### Button Components **IMPORTANT**: Most buttons are STATIC in the template. However, URL buttons with variables CAN be dynamic. | Component Name | Description | When It Appears | | --------------- | ------------------------------------------------- | ------------------------------------------------------- | | `buttons_url_0` | URL for first URL button with variable `{"{1}"}` | When template has URL button with `{"{1}"}` placeholder | | `buttons_url_1` | URL for second URL button with variable `{"{2}"}` | When template has multiple URL buttons with variables | **Example Template Button**: `"url": "https://example.com/{"{1}"}"` * The template defines the URL pattern with placeholder * You provide the value for `{"{1}"}` via `buttons_url_0` **Note**: Quick reply buttons are always static and cannot be modified per message. ### Component Examples #### Example 1: Template with Image Header (Nested Structure) ```json theme={null} { "components_push": { "header_image": "https://cdn.example.com/promo.jpg" } } ``` #### Example 2: Template with Body Variables (Nested Structure) ```json theme={null} { "components_push": { "body_text_0": "María", "body_text_1": "JavaScript Advanced Course" } } ``` #### Example 3: Template with Image and Variables (Flat Structure) ```json theme={null} { "components_push.header_image": "https://cdn.example.com/promo.jpg", "components_push.body_text_0": "María", "components_push.body_text_1": "JavaScript Advanced Course" } ``` *** ## 🧩 Template Variables (Body Text) **CRITICAL**: Variables in WhatsApp templates use **positional placeholders** like `{"{1}"}`, `{"{2}"}`, `{"{3}"}`, NOT named variables like `{"{name}"}`. ### How Template Variables Work WhatsApp templates define variables as numbered placeholders in the template body text: * Template text: "Hello `{"{1}"}`, your course `{"{2}"}` is ready" * `{"{1}"}` maps to `body_text_0` * `{"{2}"}` maps to `body_text_1` ### Variable Assignment You provide the **values** for these numbered placeholders in the `components_push` object: ```json theme={null} { "components_push": { "body_text_0": "María", "body_text_1": "JavaScript Advanced Course" } } ``` **Result**: "Hello María, your course JavaScript Advanced Course is ready" ### Important Rules 1. **Positional order matters**: `body_text_0` = `{"{1}"}`, `body_text_1` = `{"{2}"}`, etc. 2. **Direct values**: Provide the actual value, not `{"{variable}"}` syntax 3. **All variables required**: Must provide values for all `{"{N}"}` placeholders in the template 4. **No mixing**: Cannot combine variables with static text in components\_push ### ✅ Valid Examples **Template**: "Hello `{"{1}"}`, your order `{"{2}"}` is ready" ```json theme={null} { "contact": { "name": "María", "order_id": "ORD-12345" }, "components_push": { "body_text_0": "María", "body_text_1": "ORD-12345" } } ``` **You can also use contact fields directly**: ```json theme={null} { "contact": { "name": "María", "order_id": "ORD-12345" }, "components_push": { "body_text_0": "{{name}}", "body_text_1": "{{order_id}}" } } ``` ### ❌ Invalid Examples ```json theme={null} { "components_push": { "body_text_0": "Hello María", // ❌ Don't add template text, only the value "body_text_1": "{1}" // ❌ Don't use {1}, provide the actual value } } ``` ### Variable Resolution The system resolves `{"{variable}"}` references in `components_push` by searching: 1. First in `contact` object 2. Then in root-level parameters ```json theme={null} { "contact": { "name": "María", "email": "maria@example.com" }, "order_id": "ORD-12345", "components_push": { "body_text_0": "{{name}}", // Resolved from contact.name → "María" "body_text_1": "{{order_id}}" // Resolved from root order_id → "ORD-12345" } } ``` *** ## ⏰ Scheduling & Conversation Flows ### Immediate Delivery (Default) If `datetime_sending` is not specified, the message is sent immediately: ```json theme={null} { "contact": { "number": "34666033135" }, "language_template": "es_ES", "name_template": "welcome_user", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Welcome Campaign" } ``` ### Scheduled Delivery Use `datetime_sending` and `timezone` to schedule messages: **Nested Structure:** ```json theme={null} { "contact": { "number": "34666033135" }, "language_template": "es_ES", "name_template": "welcome_user", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Welcome Campaign", "datetime_sending": "2024-12-25T10:30:00", "timezone": "Europe/Madrid" } ``` **Flat Structure:** ```json theme={null} { "contact.number": "34666033135", "language_template": "es_ES", "name_template": "welcome_user", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Welcome Campaign", "datetime_sending": "2024-12-25T10:30:00", "timezone": "Europe/Madrid" } ``` ### Conversation Flow Integration Use `name_event` to trigger specific conversation flows: ```json theme={null} { "contact": { "number": "34666033135" }, "language_template": "es_ES", "name_template": "course_info", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Course Enrollment", "name_event": "info-course-nursing" } ``` *** ## 📋 Complete Examples ### Example 1: Single Recipient with Body Variables **Sending to ONE person using `contact`** **Template**: "Hello `{"{1}"}`, thank you for your interest in `{"{2}"}`!" **Request:** ```json theme={null} { "contact": { "number": "34666033135", "customer_name": "Sarah" }, "components_push": { "body_text_0": "{{customer_name}}", "body_text_1": "our Premium Membership" }, "language_template": "en", "name_template": "interest_confirmation", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Membership Campaign 2024" } ``` **Response:** ```json theme={null} { "message": "Registration successful", "inquiry_set_id": "d891b9b4-2f79-45a0-bc99-9a2922d4f713" } ``` *** ### Example 1B: Multiple Recipients (Bulk Sending) **Sending to MULTIPLE people using `contacts`** **Template**: "Hello `{"{1}"}`, thank you for your interest in `{"{2}"}`!" **Request:** ```json theme={null} { "contacts": [ { "number": "34666033135", "customer_name": "Sarah" }, { "number": "34777044246", "customer_name": "John" }, { "number": "34888055357", "customer_name": "Michael" } ], "components_push": { "body_text_0": "{{customer_name}}", "body_text_1": "our Premium Membership" }, "language_template": "en", "name_template": "interest_confirmation", "recipient_id": "bulk_campaign", "notification_type": "marketing", "campaign_name": "Membership Campaign 2024" } ``` **Response:** ```json theme={null} { "success": true, "message": "Processing completed", "total": 3, "processed": 3, "failed": 0, "errors": [] } ``` **If some contacts fail:** ```json theme={null} { "success": true, "message": "Processing completed", "total": 3, "processed": 2, "failed": 1, "errors": [ { "contact": { "number": "34888055357", "customer_name": "Michael" }, "reason": "Invalid phone number format" } ] } ``` *** ### Example 2: Template with Image Header **Template components**: `["header_image"]` **URL:** ``` POST https://cloud.lovi.ai/functions/v1/notify?access_key=your-api-key ``` **Nested Structure:** ```json theme={null} { "contact": { "number": "34666033135", "customer_name": "Robert", "email": "robert@example.com" }, "components_push": { "header_image": "https://cdn.example.com/seasonal-promotion.jpg" }, "language_template": "en", "name_template": "seasonal_offer", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Holiday Sale 2024", "datetime_sending": "2024-12-24T09:00:00", "timezone": "Europe/Madrid" } ``` **Note**: Body, footer, and buttons are static in the template and cannot be modified. ### Example 3: Template with PDF Document **Template components**: `["header_document"]` **URL:** ``` POST https://cloud.lovi.ai/functions/v1/notify?access_key=your-api-key&unflatten=true ``` **Flat Structure:** ```json theme={null} { "contact.number": "34666033135", "contact.customer_name": "Jennifer", "contact.email": "jennifer@example.com", "components_push.header_document": "https://files.example.com/statement-2024.pdf", "language_template": "en", "name_template": "document_delivery", "recipient_id": "34666033135", "notification_type": "transactional", "campaign_name": "Monthly Statements" } ``` ### Example 4: Conversation Flow Trigger **Nested Structure:** ```json theme={null} { "contact": { "number": "34666033135", "first_name": "Laura", "last_name": "Martínez", "course_interest": "Web Development" }, "language_template": "en_US", "name_template": "course_inquiry", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Course Enrollment Q1", "name_event": "course-web-development-info", "datetime_sending": "2024-12-26T10:00:00", "timezone": "Europe/Madrid" } ``` *** ## 📊 Response Codes ### Successful Response (200 OK) Single `contact`: ```json theme={null} { "message": "Registration successful", "inquiry_set_id": "d891b9b4-2f79-45a0-bc99-9a2922d4f713" } ``` Array `contacts` (up to 100): `message` is `"Processing completed"` plus `total`, `processed`, `failed`, and `errors`. There is no `notification_id` on this API. Authentication is the `access_key` query parameter (no `Authorization` header). **Create** requires `whatsapp_notifications` on the key. **Status** does not: any valid key for the same company can query. ### Error Responses Error bodies are `{ "success": false, "message": "…" }`. #### 400 Bad Request Missing `access_key`, invalid body, or (on status) missing `campaign_id`. ```json theme={null} { "success": false, "message": "Required field missing" } ``` #### 403 Forbidden — Create without permission The key lacks `whatsapp_notifications` (or `voice_notifications` on `POST /notify/voice`). ```json theme={null} { "success": false, "message": "Forbidden" } ``` #### 404 Not Found — Invalid key or missing campaign An invalid `access_key` returns **404**, not 401 (`Invalid access_key`). The same 404 is used when the campaign is missing or belongs to another company (`Campaign not found`). ```json theme={null} { "success": false, "message": "Invalid access_key" } ``` #### 500 Internal Server Error ```json theme={null} { "success": false, "message": "Request failed" } ``` *** ## Query campaign status After you create a campaign, poll progress with `GET`. Pass `campaign_id` = the `inquiry_set_id` from the create response. **The same status endpoint covers WhatsApp and voice** (`POST /notify/voice`). ### Endpoint Production: ```http theme={null} GET https://cloud.lovi.ai/functions/v1/notify/status?access_key={YOUR_ACCESS_KEY}&campaign_id={CAMPAIGN_ID} ``` Verified host: `https://cloud.lovi.ai/functions/v1/…`. `https://app.lovi.ai/functions/...` is not this API (it serves the frontend). `app-dev.lovi.ai` is not documented here as a functions host. Keep `access_key` secret. Examples use placeholders only. ### Query parameters | Parameter | Required | Description | | ------------- | -------- | ------------------------------------------- | | `access_key` | Yes | Valid key. Only campaigns for that company. | | `campaign_id` | Yes | UUID = `inquiry_set_id` from create. | ### Successful response example ```json theme={null} { "campaign_id": "d891b9b4-2f79-45a0-bc99-9a2922d4f713", "name": "Membership Campaign 2026", "status": "processing", "general_failure": false, "alert_message": null, "created_at": "2026-08-10T09:30:00Z", "total_recipients": 100, "sent": 76, "failed": 4, "pending": 20, "responded": 18, "by_status": { "delivered": 61, "read": 15, "failed": 4, "pending": 20 } } ``` ### Field meanings | Field | Description | | ------------------ | -------------------------------------------------------------------------------------------------------- | | `campaign_id` | Campaign being queried. | | `name` | Campaign name. | | `status` | Observed values: `pending`, `processing`, `ready`, `completed`, `failed` (varchar, not a Postgres ENUM). | | `general_failure` | `true` when overall status is `failed`. | | `alert_message` | Alert text, if any. | | `created_at` | Creation time. | | `total_recipients` | Registered recipients. | | `sent` | Recipients in `delivered`, `read`, `sent`, `completed`, or `done`. | | `failed` | Recipients in `failed`, `warning`, `busy`, `no-answer`, or `voicemail`. | | `pending` | Everyone else. | | `responded` | Distinct chats for the campaign with at least one user message. | | `by_status` | Counts by original recipient status. | Counters can change while sending is in progress. No HTTP rate limit and no pagination on this endpoint. ### Possible errors | Code | Cause | | ----- | --------------------------------------------------------------------------------- | | `400` | Missing `campaign_id` or `access_key`. | | `404` | `Invalid access_key`, or campaign missing / other company (`Campaign not found`). | | `500` | Status lookup failed. | *** *** ## 🔧 Best Practices ### Data Structure * **Prefer nested structure** (`unflatten=false`) for better readability * **Use flat structure** (`unflatten=true`) only when your system requires it * **Validate structure** before sending requests ### Media Guidelines * **Use HTTPS URLs** for all media files * **Optimize file sizes** for faster delivery * **Use public URLs** without authentication requirements * **Test media URLs** before sending to ensure accessibility ### Scheduling * **Specify timezone** when using `datetime_sending` * **Validate future dates** before scheduling * **Consider business hours** for better engagement * **Test scheduling** in development environment ### Placeholders * **Use meaningful variable names** that match your data * **Test variable substitution** before production * **Keep one variable per field** to avoid errors * **Provide fallback values** in your application logic ### Performance * **Batch multiple notifications** when possible * **Cache template information** to reduce API calls * **Monitor rate limits** and implement backoff strategies * **Use connection pooling** for better performance *** ## 🚨 Common Errors & Solutions ### Contact/Contacts Validation Errors #### Error: Missing Contact Information **Problem**: Request doesn't include `contact` or `contacts` ```json theme={null} { "error": "validation_failed", "message": "Missing required field: 'contact' or 'contacts'" } ``` **Solution**: You must include either `contact` (for one person) OR `contacts` (for multiple people) *** #### Error: Using Both Contact and Contacts **Problem**: Request includes both `contact` and `contacts` at the same time ```json theme={null} { "error": "validation_failed", "message": "Cannot use both 'contact' and 'contacts' simultaneously" } ``` **Solution**: Choose only one - use `contact` for single recipient or `contacts` for multiple recipients *** #### Error: Contact is a List Instead of Object **Problem**: Sent `contact` as a list `[...]` instead of an object `{...}` ```json theme={null} { "error": "validation_failed", "message": "'contact' must be an object, not an array" } ``` **Solution**: For single recipient, use `contact` as an object: ```json theme={null} { "contact": { "number": "34666033135", "name": "John" } } ``` *** #### Error: Contacts is Not a List **Problem**: Sent `contacts` as an object `{...}` instead of a list `[...]` ```json theme={null} { "error": "validation_failed", "message": "'contacts' must be an array, not an object" } ``` **Solution**: For multiple recipients, use `contacts` as a list: ```json theme={null} { "contacts": [ { "number": "34666033135", "name": "John" }, { "number": "34777044246", "name": "Sarah" } ] } ``` *** #### Error: Empty Contacts List **Problem**: Sent `contacts` as an empty list `[]` ```json theme={null} { "error": "validation_failed", "message": "'contacts' array cannot be empty" } ``` **Solution**: Include at least one contact in the list *** #### Error: Too Many Contacts **Problem**: Sent more than 100 contacts in the `contacts` list ```json theme={null} { "error": "validation_failed", "message": "Maximum 100 contacts allowed per request. Received: 150" } ``` **Solution**: Split your contacts into multiple requests of maximum 100 contacts each *** #### Error: Unflatten with Contacts **Problem**: Used `unflatten=true` with `contacts` (bulk sending) ```json theme={null} { "error": "validation_failed", "message": "Parameter 'unflatten' is incompatible with 'contacts' array" } ``` **Solution**: Bulk sending with `contacts` only works with nested structure. Remove `unflatten=true` or use `unflatten=false` *** ### Structure Mismatch **Problem**: Mixing nested and flat structures **Solution**: Choose one format consistently based on `unflatten` parameter ### Template Not Found **Problem**: Using non-existent or non-approved template **Solution**: Verify template name and approval status using template management endpoints ### Invalid Phone Format **Problem**: Including '+' or spaces in phone number **Solution**: Use clean international format without symbols (e.g., `34666033135`) ### Component Naming Errors **Problem**: Using incorrect component names like `header_image_0` instead of `header_image` **Solution**: Always check template components first using the `/notify/template/components` endpoint ### Variable Position Errors **Problem**: Incorrect mapping between template placeholders and body\_text components **Solution**: Remember `{"{1}"}` = `body_text_0`, `{"{2}"}` = `body_text_1`, etc. ### Modifying Static Components **Problem**: Trying to send footer, buttons, or header text dynamically **Solution**: These components are static in the template. Only send components returned by the template components endpoint ### Scheduling Errors **Problem**: Past dates or invalid timezone **Solution**: Use future dates in ISO 8601 format with valid IANA timezone codes *** ## 🔬 Template Examples These examples demonstrate different types of template structures. ### Example 1: Simple Text Only Template **Components returned by API**: `[]` (empty - no dynamic components) ```json theme={null} { "contact": { "number": "34666033135" }, "language_template": "en", "name_template": "welcome_message", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Welcome Campaign" } ``` **Note**: No `components_push` needed since template has no dynamic components. *** ### Example 2: Template with Body Variables **Template Body**: "Hello `{"{1}"}`, your `{"{2}"}` is ready!" **Components returned by API**: `["body_text_0", "body_text_1"]` ```json theme={null} { "contact": { "number": "34666033135", "customer_name": "John", "product_name": "Premium Package" }, "components_push": { "body_text_0": "{{customer_name}}", "body_text_1": "{{product_name}}" }, "language_template": "en", "name_template": "order_ready", "recipient_id": "34666033135", "notification_type": "transactional", "campaign_name": "Order Notifications" } ``` **Result**: "Hello John, your Premium Package is ready!" *** ### Example 3: Template with Image Header **Components returned by API**: `["header_image"]` ```json theme={null} { "contact": { "number": "34666033135" }, "components_push": { "header_image": "https://example.com/promotional-banner.jpg" }, "language_template": "en", "name_template": "promo_with_image", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Holiday Sale" } ``` **Note**: Body, footer, and buttons are static in the template. *** ### Example 4: Template with Video Header **Components returned by API**: `["header_video"]` ```json theme={null} { "contact": { "number": "34666033135" }, "components_push": { "header_video": "https://example.com/product-demo.mp4" }, "language_template": "en", "name_template": "video_announcement", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Product Launch" } ``` *** ### Example 5: Template with PDF Document **Components returned by API**: `["header_document"]` ```json theme={null} { "contact": { "number": "34666033135", "customer_name": "Jane Smith" }, "components_push": { "header_document": "https://example.com/invoice-12345.pdf" }, "language_template": "en", "name_template": "invoice_delivery", "recipient_id": "34666033135", "notification_type": "transactional", "campaign_name": "Monthly Invoices" } ``` *** ### Example 6: Template with Header Text and Body Variable **Header**: "Important Notice" (static text in template) **Body**: "Hello `{"{1}"}`, welcome to our platform!" **Components returned by API**: `["body_text_0"]` ```json theme={null} { "contact": { "number": "34666033135", "first_name": "Michael" }, "components_push": { "body_text_0": "{{first_name}}" }, "language_template": "en", "name_template": "welcome_with_header", "recipient_id": "34666033135", "notification_type": "utility", "campaign_name": "User Onboarding" } ``` **Result**: * Header: "Important Notice" * Body: "Hello Michael, welcome to our platform!" *** ### Example 7: Template with Dynamic URL Button **Body**: Static text **Buttons**: * URL button with variable: `"url": "https://example.com/{"{1}"}"` (dynamic) * Quick reply button: "Contact Support" (static) **Components returned by API**: `["buttons_url_0"]` ```json theme={null} { "contact": { "number": "34666033135", "customer_id": "12345" }, "components_push": { "buttons_url_0": "https://example.com/profile/{{customer_id}}" }, "language_template": "en", "name_template": "account_notification", "recipient_id": "34666033135", "notification_type": "utility", "campaign_name": "Account Updates" } ``` **Result**: The URL button will link to `https://example.com/profile/12345` **Note**: The variable in `buttons_url_0` can reference contact fields using `{"{variable}"}` syntax, and the system will resolve it before sending. *** ## 📚 Related Documentation * **[Authentication](./authentication)** - How to obtain access tokens and API keys * **[Voice Notifications](./voice_notifications)** - Send automated voice calls * **[Template Management](./template_management)** - Create and manage WhatsApp templates * **[Scheduling](./scheduling)** - Advanced scheduling and timezone management * **[Error Handling](./error_handling)** - Complete error codes and handling strategies * **[Best Practices](./best_practices)** - Security and performance guidelines # Authentication Source: https://docs.lovi.ai/en/api-reference/authentication Official documentation for authenticating with our API ## Introduction This section provides the official documentation for authenticating with our API via Postman. It includes detailed instructions on the authentication process, supported methods, and best practices to ensure secure access to the API. ## 🔑 Authentication To access any Lovi API endpoint, you must first authenticate and obtain an access token using your email and password. This token must then be included in the `Authorization` header of all subsequent requests. **Token Rotation**: We recommend periodically rotating tokens and storing them securely. > **Method**: POST **Format**: JSON ### Endpoint ``` POST https://cloud.lovi.ai/functions/v1/auth?grant_type=password ``` ### Query Parameters | Parameter | Required | Fixed Value | Description | | ------------ | -------- | ----------- | --------------------------------------------------------------------------------------------------------------- | | `grant_type` | Yes | `password` | Indicates that you're using authentication via email and password. This is currently the only supported method. | ### Request Headers Inform the server of the type of content you are sending: | Key | Value | Description | | -------------- | ------------------ | -------------------------------------------------- | | `Content-Type` | `application/json` | Indicates that the request body is in JSON format. | ### Request Body This is where you provide your personal credentials (your email and password): ```json theme={null} { "email": "your-email@example.com", "password": "your-secure-password" } ``` You should replace: * "[your-email@example.com](mailto:your-email@example.com)" with your actual email address. * "your-secure-password" with your password. ### Response The server will respond with a token that you will need to authenticate in future API calls. The response will look like this: ```json theme={null} { "access_token": "your-access-token", "token_type": "bearer", "expires_in": 3600, "expires_at": 1744196340, "refresh_token": "bNYf6m7S7dFjfHCFTob2zg", "user": { "id": "3b601fa8-49fd-4a8c-af8c-b30601410975", "aud": "authenticated", "role": "authenticated", "email": "your-email@example.com", "email_confirmed_at": "2025-01-16T18:45:09.995114Z", "phone": "", "confirmed_at": "2025-01-16T18:45:09.995114Z", "last_sign_in_at": "2025-04-09T09:59:00.18987884Z", "app_metadata": { "provider": "email", "providers": [ "email" ] }, "user_metadata": { "email_verified": true, "name": "Your-Company" }, "identities": [ { "identity_id": "23b66c68-ce37-4483-8470-67e08e21a61e", "id": "3b601fa8-49fd-4a8c-af8c-b30601410975", "user_id": "3b601fa8-49fd-4a8c-af8c-b30601410975", "identity_data": { "email": "your-email@example.com", "email_verified": false, "phone_verified": false, "sub": "3b601fa8-49fd-4a8c-af8c-b30601410975" }, "provider": "email", "last_sign_in_at": "2025-01-16T18:45:09.985377Z", "created_at": "2025-01-16T18:45:09.985932Z", "updated_at": "2025-01-16T18:45:09.985932Z", "email": "your-email@example.com" } ], "created_at": "2025-01-16T18:45:09.956743Z", "updated_at": "2025-04-09T09:59:00.192243Z", "is_anonymous": false } } ``` ⚠️ Save your `access_token` — you'll use it to interact with the rest of the API. *** ## 🧭 Retrieve Company API Keys > **Method**: POST **Format**: JSON ### Endpoint ``` POST https://cloud.lovi.ai/functions/v1/api_keys ``` **Headers** | **Parameter** | **Example** | **Required** | **Description** | | ------------- | ------------------------ | ------------ | --------------------------------------------------------------------------------------------------------- | | Authorization | Bearer your-access-token | Yes | This header is used for authenticating the request. It contains a Bearer token received after logging in. | ### Response Each object within the **`validApiKeys`** array represents an API key associated with a specific company. The fields included are: * **`company_id`**: Unique identifier of the company that owns the API key. * **`key`**: The API key itself, used to authenticate requests. * **`is_active`**: Boolean value indicating whether the key is currently active (*true*) or inactive (*false*). * **`last_used`**: Timestamp of the last time the key was used. If the key has never been used, the value will be *null*. * **`company_name`**: Name of the company associated with the API key, useful for display in interfaces or logs. Here's an example: ```json theme={null} { "validApiKeys": [ { "company_id": "your_company_id", "key": "your_key", "is_active": true, "last_used": null, "company_name": "your_company_name" } ] } ``` *** ### 🚫 API Access Denied (403 Forbidden) If your company has disabled API access, the server will return a `403 Forbidden` status. This occurs when `apiAccess` is configured to `false` for one or more companies associated with your account. Example: ```json theme={null} { "success": false, "message": "API access denied for some companies", "deniedCompanies": [ { "company_id": "your_company_id", "company_name": "your_company_name", "access": false } ] } ``` In this case, you must ensure that API access is enabled for your company in the configuration before you can retrieve its keys. *** ### ⚠️ No API Keys Found If the response returns an empty `validApiKeys` array, it means that no API keys have been created yet for your company. Example: ```json theme={null} { "validApiKeys": [] } ``` In this case, you **must go to the Admin Dashboard** and create a new API key for your company. In this section, you will see how to configure the profile and also how to configure the API Key from the panel. > 🧭 **Important**: Without a valid and active API key, you will not be able to authenticate at the company level or perform secured operations. Once the API key is created and active, it will appear in the `validApiKeys` array and can be used to generate a company-level token. *** #### 🔁 Token per Company Once you identify which company you want to work with, you must authenticate again to generate a company-level token. This is usually required for secured actions like sending notifications or uploading media. You will use the previously retrieved `key` in your query parameters or headers, depending on the endpoint. # Best Practices & Guidelines Source: https://docs.lovi.ai/en/api-reference/best_practices Security, performance, and integration best practices for the Lovi API ## Introduction This guide covers security best practices, performance optimization, and integration guidelines to help you build robust applications with the Lovi API. *** ## 🔒 Security Best Practices ### API Key Management **✅ DO:** * Store API keys in environment variables or secure configuration management * Use different API keys for different environments (development, staging, production) * Rotate API keys regularly (recommended: every 90 days) * Monitor API key usage and set up alerts for unusual activity * Use the principle of least privilege (separate keys for different functions if available) **❌ DON'T:** * Hardcode API keys in source code or commit them to version control * Share API keys via email, chat, or other unsecured channels * Use production API keys in development environments * Log complete API keys in application logs ### Authentication Security ```javascript theme={null} // ✅ Good - Store in environment variables const ACCESS_KEY = process.env.LOVI_ACCESS_KEY; // ❌ Bad - Hardcoded in source const ACCESS_KEY = 'your-actual-api-key-here'; ``` ### Request Security **Always use HTTPS:** ```javascript theme={null} // ✅ Good - HTTPS only const BASE_URL = 'https://cloud.lovi.ai'; // ❌ Bad - Insecure HTTP const BASE_URL = 'http://cloud.lovi.ai'; ``` **Validate input data:** ```javascript theme={null} function validatePhoneNumber(number) { // Remove any non-digit characters const cleaned = number.replace(/\D/g, ''); // Check if it's a valid international format if (!/^\d{10,15}$/.test(cleaned)) { throw new Error('Invalid phone number format'); } return cleaned; } ``` *** ## ⚡ Performance Optimization ### Connection Management **Reuse HTTP connections:** ```javascript theme={null} // ✅ Good - Reuse connection pool const https = require('https'); const agent = new https.Agent({ keepAlive: true, maxSockets: 10 }); const fetch = require('node-fetch'); async function makeRequest(url, data) { return fetch(url, { method: 'POST', agent: agent, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } ``` ### Batch Processing **Process notifications in batches:** ```javascript theme={null} async function sendNotificationsBatch(notifications, batchSize = 10) { const results = []; for (let i = 0; i < notifications.length; i += batchSize) { const batch = notifications.slice(i, i + batchSize); const batchPromises = batch.map(notification => sendNotification(notification) ); const batchResults = await Promise.allSettled(batchPromises); results.push(...batchResults); // Rate limiting: wait between batches if (i + batchSize < notifications.length) { await sleep(100); // 100ms delay } } return results; } ``` ### Caching Strategies **Cache template information:** ```javascript theme={null} class TemplateCache { constructor(ttl = 3600000) { // 1 hour TTL this.cache = new Map(); this.ttl = ttl; } async getTemplate(templateName) { const cached = this.cache.get(templateName); if (cached && Date.now() - cached.timestamp < this.ttl) { return cached.data; } const template = await fetchTemplateFromAPI(templateName); this.cache.set(templateName, { data: template, timestamp: Date.now() }); return template; } } ``` *** ## 📊 Rate Limiting & Throttling ### Understanding Rate Limits | Operation | Limit | Window | | ------------------ | ------------ | -------- | | Authentication | 10 requests | 1 minute | | Notifications | 100 requests | 1 minute | | Template retrieval | 50 requests | 1 minute | | Template creation | 5 requests | 1 hour | ### Rate Limit Handling ```javascript theme={null} class RateLimitHandler { constructor() { this.requestQueue = []; this.processing = false; } async makeRequest(url, data) { return new Promise((resolve, reject) => { this.requestQueue.push({ url, data, resolve, reject }); this.processQueue(); }); } async processQueue() { if (this.processing || this.requestQueue.length === 0) return; this.processing = true; while (this.requestQueue.length > 0) { const request = this.requestQueue.shift(); try { const response = await this.executeRequest(request); request.resolve(response); } catch (error) { if (error.status === 429) { // Rate limited - put request back and wait this.requestQueue.unshift(request); await sleep(error.retryAfter * 1000); continue; } request.reject(error); } // Small delay between requests await sleep(100); } this.processing = false; } } ``` *** ## 🏗️ Architecture Patterns ### Repository Pattern ```javascript theme={null} class NotificationRepository { constructor(apiClient) { this.apiClient = apiClient; } async sendWhatsAppNotification(notification) { const payload = this.formatWhatsAppPayload(notification); return await this.apiClient.post('/notify', payload); } async sendVoiceNotification(notification) { const payload = this.formatVoicePayload(notification); return await this.apiClient.post('/notify/voice', payload); } formatWhatsAppPayload(notification) { return { contact: { number: this.validatePhoneNumber(notification.phoneNumber), name: notification.contactName }, language_template: notification.language, name_template: notification.templateName, recipient_id: notification.phoneNumber, notification_type: notification.type, campaign_name: notification.campaign }; } } ``` ### Factory Pattern for Notifications ```javascript theme={null} class NotificationFactory { static create(type, data) { switch (type) { case 'whatsapp': return new WhatsAppNotification(data); case 'voice': return new VoiceNotification(data); default: throw new Error(`Unknown notification type: ${type}`); } } } class WhatsAppNotification { constructor(data) { this.contact = { number: data.phoneNumber, name: data.name }; this.template = data.templateName; this.type = 'whatsapp'; } toPayload() { return { contact: this.contact, name_template: this.template, // ... other properties }; } } ``` *** ## 🧪 Testing Strategies ### Unit Testing ```javascript theme={null} // Mock API responses for testing const mockAPIClient = { post: jest.fn() }; describe('NotificationService', () => { test('should send WhatsApp notification successfully', async () => { mockAPIClient.post.mockResolvedValue({ success: true, notification_id: 'test-123' }); const service = new NotificationService(mockAPIClient); const result = await service.sendWhatsApp({ phoneNumber: '34666033135', templateName: 'welcome', contactName: 'John Doe' }); expect(result.success).toBe(true); expect(mockAPIClient.post).toHaveBeenCalledWith('/notify', expect.any(Object)); }); }); ``` ### Integration Testing ```javascript theme={null} // Use test environment with real API calls describe('Integration Tests', () => { const testApiKey = process.env.TEST_API_KEY; const apiClient = new LoviAPIClient(testApiKey); test('should handle authentication flow', async () => { const authResult = await apiClient.authenticate( process.env.TEST_EMAIL, process.env.TEST_PASSWORD ); expect(authResult.access_token).toBeDefined(); expect(authResult.token_type).toBe('bearer'); }); }); ``` *** ## 🔍 Monitoring & Logging ### Structured Logging ```javascript theme={null} const logger = require('winston'); logger.configure({ format: logger.format.combine( logger.format.timestamp(), logger.format.errors({ stack: true }), logger.format.json() ), transports: [ new logger.transports.File({ filename: 'api-calls.log' }) ] }); async function loggedAPICall(endpoint, data) { const requestId = generateRequestId(); logger.info('API request started', { request_id: requestId, endpoint: endpoint, method: 'POST', timestamp: new Date().toISOString() }); try { const response = await makeAPICall(endpoint, data); logger.info('API request completed', { request_id: requestId, endpoint: endpoint, status_code: response.status, success: true, duration: Date.now() - startTime }); return response; } catch (error) { logger.error('API request failed', { request_id: requestId, endpoint: endpoint, error: error.message, status_code: error.statusCode, duration: Date.now() - startTime }); throw error; } } ``` ### Health Checks ```javascript theme={null} class APIHealthMonitor { constructor(apiClient) { this.apiClient = apiClient; this.healthStatus = { healthy: true, lastCheck: null }; } async checkHealth() { try { const start = Date.now(); await this.apiClient.get('/templates?limit=1'); const duration = Date.now() - start; this.healthStatus = { healthy: true, lastCheck: new Date().toISOString(), responseTime: duration, status: 'operational' }; } catch (error) { this.healthStatus = { healthy: false, lastCheck: new Date().toISOString(), error: error.message, status: 'degraded' }; } return this.healthStatus; } } ``` *** ## 📱 Mobile & Frontend Integration ### React Hook Example ```javascript theme={null} import { useState, useEffect } from 'react'; function useLoviNotification() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const sendNotification = async (notificationData) => { setLoading(true); setError(null); try { const response = await fetch('/api/send-notification', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(notificationData) }); if (!response.ok) { throw new Error('Failed to send notification'); } const result = await response.json(); return result; } catch (err) { setError(err.message); throw err; } finally { setLoading(false); } }; return { sendNotification, loading, error }; } ``` *** ## 🔧 Configuration Management ### Environment Configuration ```javascript theme={null} // config/lovi.js module.exports = { development: { baseURL: 'https://cloud.lovi.ai', accessKey: process.env.LOVI_DEV_ACCESS_KEY, rateLimits: { requests: 10, window: 60000 // 1 minute }, retries: 3, timeout: 30000 }, production: { baseURL: 'https://cloud.lovi.ai', accessKey: process.env.LOVI_PROD_ACCESS_KEY, rateLimits: { requests: 100, window: 60000 }, retries: 5, timeout: 60000 } }; ``` ### Client Configuration ```javascript theme={null} class LoviAPIClient { constructor(config) { this.baseURL = config.baseURL; this.accessKey = config.accessKey; this.timeout = config.timeout; this.rateLimiter = new RateLimiter(config.rateLimits); this.retryCount = config.retries; } async request(endpoint, options = {}) { await this.rateLimiter.acquire(); const url = `${this.baseURL}${endpoint}?access_key=${this.accessKey}`; const config = { timeout: this.timeout, ...options }; return this.retryRequest(url, config); } } ``` *** ## 📚 Documentation & Team Guidelines ### Code Documentation ```javascript theme={null} /** * Send a WhatsApp notification using a template * * @param {Object} notification - Notification data * @param {string} notification.phoneNumber - Recipient phone number (international format, no +) * @param {string} notification.templateName - Name of approved template * @param {string} notification.language - Language code (e.g., 'es_ES', 'en_US') * @param {Object} notification.variables - Template variables * @param {string} [notification.scheduledTime] - ISO 8601 datetime for scheduling * @param {string} [notification.timezone] - IANA timezone code * * @returns {Promise} API response with notification_id * * @throws {ValidationError} When required parameters are missing * @throws {APIError} When API request fails * * @example * const result = await sendWhatsAppNotification({ * phoneNumber: '34666033135', * templateName: 'welcome_user', * language: 'es_ES', * variables: { name: 'Juan', course: 'JavaScript' } * }); */ async function sendWhatsAppNotification(notification) { // Implementation... } ``` ### Team Standards **API Integration Checklist:** * ✅ Implement proper error handling * ✅ Add request/response logging * ✅ Include unit tests * ✅ Validate input parameters * ✅ Handle rate limiting * ✅ Use environment-specific configurations * ✅ Document all functions and classes * ✅ Implement health checks * ✅ Add monitoring and alerts *** ## 🚨 Common Pitfalls & Solutions ### Pitfall: Hardcoded Values ```javascript theme={null} // ❌ Bad const templateName = 'welcome_template_final_v2'; // ✅ Good const templateName = config.templates.welcome; ``` ### Pitfall: Poor Error Handling ```javascript theme={null} // ❌ Bad try { await sendNotification(data); } catch (error) { console.log('Error:', error); } // ✅ Good try { await sendNotification(data); } catch (error) { logger.error('Notification failed', { error: error.message, data: sanitizeForLog(data), stack: error.stack }); throw new NotificationError( 'Failed to send notification', error.statusCode, error.requestId ); } ``` ### Pitfall: Ignoring Rate Limits ```javascript theme={null} // ❌ Bad - Send all at once notifications.forEach(notification => { sendNotification(notification); }); // ✅ Good - Respect rate limits await sendNotificationsBatch(notifications, 10); ``` Remember: Following these best practices will help you build more reliable, secure, and maintainable integrations with the Lovi API. # Error Handling & Response Codes Source: https://docs.lovi.ai/en/api-reference/error_handling Comprehensive guide for handling API errors and response codes ## Introduction This guide covers all possible response codes, error formats, and best practices for handling errors when integrating with the Lovi API. ## 📊 HTTP Status Codes ### Successful Responses #### 200 OK - Request Successful **Notification Processed:** ```json theme={null} { "success": true, "message": "Notification queued successfully", "notification_id": "uuid-notification-123", "scheduled_time": "2024-12-25T10:30:00Z" } ``` **Templates Retrieved:** ```json theme={null} { "templates": [ { "id": "template_001", "name": "welcome_user", "status": "approved" } ], "total": 1 } ``` **Template Created:** ```json theme={null} { "success": true, "message": "Template created successfully", "template": { "id": "new_template_123", "name": "order_confirmation", "status": "pending" } } ``` *** ### Client Error Responses (4xx) #### 400 Bad Request - Invalid Parameters **Missing Required Field:** ```json theme={null} { "error": "validation_failed", "message": "Required field missing", "details": { "field": "contact.number", "reason": "This field is required" }, "request_id": "req_123456789" } ``` **Invalid Parameter Format:** ```json theme={null} { "error": "validation_failed", "message": "Invalid parameter format", "details": { "field": "datetime_sending", "provided": "25-12-2024", "expected_format": "ISO 8601 (YYYY-MM-DDTHH:MM:SS)", "example": "2024-12-25T10:30:00" }, "request_id": "req_123456790" } ``` **Invalid Phone Number:** ```json theme={null} { "error": "validation_failed", "message": "Invalid phone number format", "details": { "field": "contact.number", "provided": "+34-666-033-135", "expected_format": "Numbers only, no + or spaces", "example": "34666033135" }, "request_id": "req_123456791" } ``` **Past Scheduled Time:** ```json theme={null} { "error": "validation_failed", "message": "Scheduled time must be in the future", "details": { "field": "datetime_sending", "provided": "2024-12-20T10:00:00", "current_time": "2024-12-20T15:30:00Z" }, "request_id": "req_123456792" } ``` #### 401 Unauthorized - Authentication Failed **Invalid Access Key:** ```json theme={null} { "error": "unauthorized", "message": "Invalid or expired access key", "details": { "reason": "The provided access_key is not valid or has been revoked" }, "request_id": "req_123456793" } ``` **Missing Access Key:** ```json theme={null} { "error": "unauthorized", "message": "Missing access key", "details": { "reason": "access_key parameter is required in the URL" }, "request_id": "req_123456794" } ``` #### 403 Forbidden - Access Denied **Insufficient Permissions:** ```json theme={null} { "error": "forbidden", "message": "Insufficient permissions for this resource", "details": { "reason": "Your API key does not have permission to create templates" }, "request_id": "req_123456795" } ``` **Company Access Denied:** ```json theme={null} { "error": "forbidden", "message": "Access denied to company resources", "details": { "reason": "API key is not associated with the requested company" }, "request_id": "req_123456796" } ``` #### 404 Not Found - Resource Not Found **Template Not Found:** ```json theme={null} { "error": "template_not_found", "message": "Template 'welcome_user' not found or not approved", "details": { "template_name": "welcome_user", "language": "es_ES", "suggestion": "Check template name and approval status" }, "request_id": "req_123456797" } ``` **Agent Not Found:** ```json theme={null} { "error": "agent_not_found", "message": "Voice agent not found", "details": { "agent_id": "uuid-invalid-agent", "suggestion": "Verify agent_id exists and is active" }, "request_id": "req_123456798" } ``` #### 422 Unprocessable Entity - Business Logic Error **Template Not Approved:** ```json theme={null} { "error": "template_not_approved", "message": "Template is not in approved status", "details": { "template_name": "pending_template", "current_status": "pending", "required_status": "approved" }, "request_id": "req_123456799" } ``` **Variable Mismatch:** ```json theme={null} { "error": "variable_mismatch", "message": "Template variables don't match provided data", "details": { "template_variables": ["name", "course"], "provided_variables": ["name", "email"], "missing": ["course"], "unexpected": ["email"] }, "request_id": "req_123456800" } ``` #### 429 Too Many Requests - Rate Limit Exceeded **Rate Limit Hit:** ```json theme={null} { "error": "rate_limit_exceeded", "message": "Too many requests. Try again in 60 seconds", "details": { "limit": 100, "window": "1 hour", "reset_time": "2024-12-20T16:30:00Z" }, "retry_after": 60, "request_id": "req_123456801" } ``` **Daily Quota Exceeded:** ```json theme={null} { "error": "quota_exceeded", "message": "Daily notification quota exceeded", "details": { "quota": 10000, "used": 10000, "reset_time": "2024-12-21T00:00:00Z" }, "request_id": "req_123456802" } ``` *** ### Server Error Responses (5xx) #### 500 Internal Server Error **General Server Error:** ```json theme={null} { "error": "internal_error", "message": "An unexpected error occurred", "details": { "reason": "Internal server error" }, "request_id": "req_123456803" } ``` #### 502 Bad Gateway **Upstream Service Error:** ```json theme={null} { "error": "service_unavailable", "message": "WhatsApp service temporarily unavailable", "details": { "service": "whatsapp_gateway", "estimated_recovery": "2024-12-20T16:00:00Z" }, "request_id": "req_123456804" } ``` #### 503 Service Unavailable **Maintenance Mode:** ```json theme={null} { "error": "service_unavailable", "message": "Service temporarily unavailable for maintenance", "details": { "maintenance_window": "2024-12-20T02:00:00Z to 2024-12-20T04:00:00Z" }, "request_id": "req_123456805" } ``` *** ## 🔄 Retry Strategies ### Recommended Retry Logic | Status Code | Action | Retry Strategy | | --------------- | ------------------------------------- | ------------------------- | | `400, 404, 422` | ❌ **Don't retry** | Fix request and try again | | `401, 403` | ❌ **Don't retry** | Refresh authentication | | `429` | ⏰ **Retry with backoff** | Use `retry_after` header | | `500, 502, 503` | 🔄 **Retry with exponential backoff** | Max 3 attempts | ### Implementation Example ```javascript theme={null} async function makeAPIRequest(url, data, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (response.ok) { return result; } // Handle different error types if (response.status === 429) { const retryAfter = result.retry_after || 60; await sleep(retryAfter * 1000); continue; } if ([500, 502, 503].includes(response.status)) { if (attempt < maxRetries) { await sleep(Math.pow(2, attempt) * 1000); // Exponential backoff continue; } } // Don't retry for 4xx errors (except 429) throw new APIError(result.error, result.message, response.status); } catch (error) { if (attempt === maxRetries) throw error; await sleep(Math.pow(2, attempt) * 1000); } } } ``` *** ## 📝 Best Practices ### Error Logging **Always log these fields:** ```javascript theme={null} { "timestamp": "2024-12-20T15:30:00Z", "request_id": "req_123456789", "endpoint": "/notify", "method": "POST", "status_code": 400, "error_code": "validation_failed", "error_message": "Required field missing", "user_id": "user_123", "company_id": "company_456" } ``` ### Error Handling Checklist ✅ **Pre-request Validation** * Validate phone number format * Check required fields * Validate datetime format * Verify template exists ✅ **Request Monitoring** * Log all API requests * Include request\_id in logs * Monitor response times * Track error rates ✅ **Error Recovery** * Implement appropriate retry logic * Cache authentication tokens * Handle rate limits gracefully * Provide meaningful error messages to users ✅ **User Experience** * Show user-friendly error messages * Provide actionable feedback * Don't expose internal error details * Offer alternatives when possible ### Monitoring & Alerting **Key Metrics to Monitor:** * Error rate per endpoint * Average response time * Rate limit hits * Authentication failures * Template not found errors **Alert Thresholds:** * Error rate > 5% * Response time > 2 seconds * Rate limit hits > 10/hour * Authentication failures > 20/hour *** ## 🛠️ Development Tools ### Error Testing **Test different error scenarios:** ```bash theme={null} # Missing access_key curl -X POST https://cloud.lovi.ai/functions/v1/notify \ -H "Content-Type: application/json" \ -d '{"contact":{"number":"34666033135"}}' # Invalid phone number curl -X POST https://cloud.lovi.ai/functions/v1/notify?access_key=test \ -H "Content-Type: application/json" \ -d '{"contact":{"number":"+34-666-033-135"}}' # Past datetime curl -X POST https://cloud.lovi.ai/functions/v1/notify?access_key=test \ -H "Content-Type: application/json" \ -d '{"datetime_sending":"2020-01-01T10:00:00"}' ``` ### Error Response Parser ```javascript theme={null} class LoviAPIError extends Error { constructor(response) { super(response.message); this.name = 'LoviAPIError'; this.code = response.error; this.statusCode = response.status; this.requestId = response.request_id; this.details = response.details; } isRetryable() { return [429, 500, 502, 503].includes(this.statusCode); } getRetryAfter() { return this.details?.retry_after || 60; } } ``` *** ## 🚨 Common Issues & Solutions ### Authentication Issues **Problem**: `Invalid or expired access key` **Solution**: 1. Check if access\_key is in URL parameters 2. Verify the key hasn't been revoked 3. Ensure you're using the correct company key ### Template Issues **Problem**: `Template not found` **Solutions**: 1. Verify template name spelling 2. Check template language matches 3. Ensure template is approved 4. Use GET /templates to list available templates ### Rate Limiting **Problem**: `Too many requests` **Solutions**: 1. Implement exponential backoff 2. Respect retry\_after headers 3. Batch requests when possible 4. Monitor usage patterns ### Data Validation **Problem**: `Validation failed` **Solutions**: 1. Validate data before sending 2. Use proper phone number format 3. Check datetime format 4. Verify required fields are present Remember to always include the `request_id` when contacting support for faster resolution of issues. # Scheduling & Timezone Management Source: https://docs.lovi.ai/en/api-reference/scheduling Guide for scheduling notifications with timezone support ## Introduction All Lovi notification endpoints support scheduling for future delivery. This feature allows you to send notifications at optimal times considering recipient timezones and business hours. ## 📅 Scheduling Parameters Both WhatsApp and Voice notifications support the following scheduling parameters: ### Parameters | Parameter | Type | Required | Description | Example | | ------------------ | -------- | -------- | -------------------------------------- | ----------------------- | | `datetime_sending` | DateTime | No | Scheduled date/time in ISO 8601 format | `"2024-12-25T10:30:00"` | | `timezone` | String | No | Timezone for the scheduled time | `"Europe/Madrid"` | **Default Behavior**: If `datetime_sending` is not provided, the notification is sent immediately. *** ## 🕐 Datetime Format ### Supported ISO 8601 Formats ```json theme={null} { "datetime_sending": "2024-12-25T10:30:00", "timezone": "Europe/Madrid" } ``` **Alternative formats:** ``` "2024-12-25T10:30:00" "2024-12-25T10:30:00.000" "2024-12-25T10:30:00Z" "2024-12-25T10:30:00+01:00" ``` ### Important Notes * **Future Date Required**: The datetime must be in the future * **ISO 8601 Strict**: Use standard ISO 8601 format * **Timezone Recommended**: Always specify timezone for scheduled messages * **UTC Default**: If timezone is omitted, UTC is used *** ## 🌍 Supported Timezones ### Common Business Timezones | Region/Country | Timezone Code | Description | | ----------------- | -------------------------------- | ---------------------- | | **Spain** | `Europe/Madrid` | Central European Time | | **Mexico** | `America/Mexico_City` | Central Standard Time | | **Argentina** | `America/Argentina/Buenos_Aires` | Argentina Time | | **Colombia** | `America/Bogota` | Colombia Time | | **Chile** | `America/Santiago` | Chile Standard Time | | **Peru** | `America/Lima` | Peru Time | | **Ecuador** | `America/Guayaquil` | Ecuador Time | | **Venezuela** | `America/Caracas` | Venezuela Time | | **United States** | `America/New_York` | Eastern Standard Time | | **United States** | `America/Chicago` | Central Standard Time | | **United States** | `America/Denver` | Mountain Standard Time | | **United States** | `America/Los_Angeles` | Pacific Standard Time | | **UTC** | `UTC` | Coordinated Universal | ### European Timezones | Country | Timezone Code | Description | | ------------------ | ------------------ | --------------------- | | **United Kingdom** | `Europe/London` | Greenwich Mean Time | | **France** | `Europe/Paris` | Central European Time | | **Germany** | `Europe/Berlin` | Central European Time | | **Italy** | `Europe/Rome` | Central European Time | | **Netherlands** | `Europe/Amsterdam` | Central European Time | | **Portugal** | `Europe/Lisbon` | Western European Time | *** ## 📋 Examples ### WhatsApp Scheduled Notification **Immediate sending (no scheduling):** ```json theme={null} { "contact": { "number": "34666033135", "name": "Ana García" }, "language_template": "es_ES", "name_template": "promocion_navidad", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Christmas Campaign" } ``` **Scheduled for specific time:** ```json theme={null} { "contact": { "number": "34666033135", "name": "Ana García" }, "language_template": "es_ES", "name_template": "promocion_navidad", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Christmas Campaign", "datetime_sending": "2024-12-24T09:00:00", "timezone": "Europe/Madrid" } ``` ### Voice Scheduled Notification ```json theme={null} { "contact": { "number": "34666033135", "first_name": "María", "last_name": "González" }, "recipient_id": "34911670470", "agent_id": "uuid-agente-voz", "notification_type": "marketing", "campaign_name": "Voice Campaign", "datetime_sending": "2024-12-26T10:00:00", "timezone": "Europe/Madrid" } ``` ### Flat Structure with Scheduling (unflatten=true) ```json theme={null} { "contact.number": "34666033135", "contact.name": "Ana García", "language_template": "es_ES", "name_template": "promocion_navidad", "recipient_id": "34666033135", "notification_type": "marketing", "campaign_name": "Christmas Campaign", "datetime_sending": "2024-12-24T09:00:00", "timezone": "Europe/Madrid" } ``` *** ## ⚙️ Validation Rules ### Date Validation * ✅ **Future dates only**: Must be later than current time * ✅ **Maximum advance**: Up to 1 year in advance * ✅ **Format strict**: Must follow ISO 8601 * ❌ **Past dates**: Will be rejected * ❌ **Invalid format**: Non-ISO dates rejected ### Timezone Validation * ✅ **Valid IANA codes**: Use standard timezone database * ✅ **Case sensitive**: Exact format required * ❌ **Abbreviations**: Don't use CET, EST, etc. * ❌ **Invalid codes**: Unknown timezones rejected ### Examples of Valid/Invalid **✅ Valid:** ```json theme={null} { "datetime_sending": "2024-12-25T14:30:00", "timezone": "America/Mexico_City" } ``` **❌ Invalid datetime:** ```json theme={null} { "datetime_sending": "12/25/2024 2:30 PM", // Wrong format "timezone": "Europe/Madrid" } ``` **❌ Invalid timezone:** ```json theme={null} { "datetime_sending": "2024-12-25T14:30:00", "timezone": "CET" // Use Europe/Madrid instead } ``` *** ## 🚀 Best Practices ### Optimal Timing **WhatsApp Messages:** * **Business hours**: 9:00 AM - 6:00 PM local time * **Avoid early/late**: Not before 8:00 AM or after 9:00 PM * **Weekend consideration**: Adjust for weekend preferences * **Holiday awareness**: Check local holidays **Voice Calls:** * **Business hours only**: 9:00 AM - 5:00 PM local time * **Workdays preferred**: Monday through Friday * **Lunch hours**: Avoid 12:00 PM - 2:00 PM * **Cultural sensitivity**: Respect local customs ### Timezone Strategy 1. **Store user timezones**: Save preferred timezone per contact 2. **Local time display**: Show times in user's local timezone 3. **Business hour logic**: Calculate optimal sending times 4. **Daylight saving**: IANA codes handle DST automatically ### Scheduling Workflow ```mermaid theme={null} graph TD A[User Input] --> B[Validate DateTime] B --> C[Validate Timezone] C --> D[Check Future Date] D --> E[Queue Notification] E --> F[Send at Scheduled Time] ``` *** ## 📊 Response Examples ### Successful Scheduling ```json theme={null} { "success": true, "message": "Notification scheduled successfully", "notification_id": "uuid-notification-123", "scheduled_time": "2024-12-25T10:30:00Z", "local_time": "2024-12-25T11:30:00+01:00", "timezone": "Europe/Madrid" } ``` ### Immediate Sending ```json theme={null} { "success": true, "message": "Notification queued for immediate sending", "notification_id": "uuid-notification-456", "queued_at": "2024-12-20T15:45:30Z" } ``` *** ## ⚠️ Common Errors ### Past Date Error ```json theme={null} { "error": "validation_failed", "message": "Scheduled time must be in the future", "details": { "field": "datetime_sending", "provided": "2024-12-20T10:00:00", "current_time": "2024-12-20T15:30:00Z" } } ``` ### Invalid Timezone Error ```json theme={null} { "error": "validation_failed", "message": "Invalid timezone specified", "details": { "field": "timezone", "provided": "CET", "suggestion": "Use IANA timezone codes like 'Europe/Madrid'" } } ``` ### Invalid Format Error ```json theme={null} { "error": "validation_failed", "message": "Invalid datetime format", "details": { "field": "datetime_sending", "provided": "25-12-2024 10:30", "expected_format": "ISO 8601 (YYYY-MM-DDTHH:MM:SS)" } } ``` *** ## 🛠️ Development Tips ### Testing Scheduling 1. **Use near future**: Test with times 5-10 minutes ahead 2. **Timezone testing**: Test different timezone scenarios 3. **Edge cases**: Test daylight saving transitions 4. **Error handling**: Test with invalid dates/timezones ### Production Considerations * **Buffer time**: Add 2-3 minutes buffer for processing * **Monitoring**: Monitor scheduled vs actual send times * **Retry logic**: Handle temporary failures gracefully * **Logs**: Log scheduling details for debugging ### Timezone Resources * **IANA Database**: [Official timezone database](https://www.iana.org/time-zones) * **Online converter**: Use tools to verify timezone codes * **Documentation**: Keep timezone reference for developers # Template Management API Source: https://docs.lovi.ai/en/api-reference/template_management Official documentation for managing WhatsApp message templates ## Introduction Template management endpoints allow you to retrieve, inspect, and create WhatsApp message templates. Templates are pre-approved message formats required by WhatsApp for business communications. *** ## 📋 Get Available Templates Retrieve all templates available for your company and phone number. ### Endpoint ``` GET https://cloud.lovi.ai/functions/v1/notify/templates?access_key={YOUR_ACCESS_KEY}&phone_number={PHONE_NUMBER} ``` ### Query Parameters | Parameter | Required | Description | | -------------- | -------- | ---------------------------------------------------- | | `access_key` | Yes | Your unique API access key. | | `phone_number` | Yes | Phone number to filter templates (without '+' sign). | **Example Request:** ``` GET https://cloud.lovi.ai/functions/v1/notify/templates?access_key=your-api-key&phone_number=34666033135 ``` ### Response The API returns an **array** of template objects (not wrapped in an object). ```json theme={null} [ { "id": "696796403510039", "name": "welcome_message", "status": "APPROVED", "category": "MARKETING", "language": "en", "components": [ { "text": "Welcome! This is a test message.", "type": "BODY" } ], "parameter_format": "POSITIONAL" }, { "id": "1269099831552940", "name": "order_confirmation", "status": "APPROVED", "category": "MARKETING", "language": "es", "components": [ { "text": "Hello `{"{1}"}`, your order `{"{2}"}` is ready!", "type": "BODY", "example": { "body_text": [["John", "Premium Package"]] } } ], "parameter_format": "POSITIONAL" }, { "id": "2616178978766893", "name": "promo_with_image", "status": "APPROVED", "category": "MARKETING", "language": "es", "components": [ { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["https://example.com/image.jpg"] } }, { "text": "Check out our special offer!", "type": "BODY" }, { "text": "Thank you for choosing us!", "type": "FOOTER" } ], "parameter_format": "POSITIONAL" } ] ``` **Important Notes:** * Response is a **direct array**, not an object with a "templates" key * `status` values are in **UPPERCASE**: `"APPROVED"`, `"PENDING"`, `"REJECTED"` * `category` values are in **UPPERCASE**: `"MARKETING"`, `"UTILITY"`, `"AUTHENTICATION"` * Each template includes `parameter_format` (typically `"POSITIONAL"`) * `components` array shows the template structure but NOT which components are dynamic *** ## 🔍 Get Template Components Retrieve detailed information about a specific template's components and structure. ### Endpoint ``` GET https://cloud.lovi.ai/functions/v1/notify/template/components?access_key={YOUR_ACCESS_KEY}&template={TEMPLATE_ID} ``` ### Query Parameters | Parameter | Required | Description | | ------------ | -------- | ------------------------------------ | | `access_key` | Yes | Your unique API access key. | | `template` | Yes | ID or name of the template to query. | **Example Request:** ``` GET https://cloud.lovi.ai/functions/v1/notify/template/components?access_key=your-api-key&template=template_001 ``` ### Response This endpoint returns ONLY the **dynamic components** that you need to provide when sending a notification. ```json theme={null} { "status": "OK", "components": ["header_image", "body_text_0", "body_text_1"] } ``` **Response Structure:** * `status`: Always `"OK"` if successful * `components`: **Array of strings** listing the dynamic component names you must provide **Example Responses:** **Template with image header and 2 body variables:** ```json theme={null} { "status": "OK", "components": ["header_image", "body_text_0", "body_text_1"] } ``` **Template with only body variables (no media):** ```json theme={null} { "status": "OK", "components": ["body_text_0", "body_text_1"] } ``` **Template with static content only (no dynamic components):** ```json theme={null} { "status": "OK", "components": [] } ``` **Template with video header:** ```json theme={null} { "status": "OK", "components": ["header_video"] } ``` **Template with PDF document:** ```json theme={null} { "status": "OK", "components": ["header_document"] } ``` **Template with dynamic URL button:** ```json theme={null} { "status": "OK", "components": ["buttons_url_0"] } ``` **Important Notes:** * This endpoint does NOT return the full template structure * It only returns the **names** of components you need to provide when sending the notification * Footer and static buttons are NOT included (they're static in the template) * Component names follow the pattern: * Headers: `header_image`, `header_video`, `header_document` (no suffix number) * Body variables: `body_text_0`, `body_text_1`, `body_text_N` (with index) * Button URLs: `buttons_url_0`, `buttons_url_1` (with index) *** ## 🆕 Create New Template Create a new WhatsApp message template for approval by Meta. ### Endpoint ``` POST https://cloud.lovi.ai/functions/v1/admin/whatsapp/create/template ``` ### Headers | Key | Value | Required | Description | | ------------ | ---------------- | -------- | -------------------------------------------------- | | Content-Type | application/json | Yes | Indicates that the request body is in JSON format. | ### Body Request ```json theme={null} { "company_id": "uuid-company", "waba_id": "whatsapp-business-account-id", "name_template": "nuevo_template", "language_template": "es_ES", "category_template": "MARKETING", "components_template": { "HEADER": { "format": "TEXT", "text": "Important: {1}" }, "BODY": { "text": "Hello {1}, we inform you that {2}. For more information visit {3}." }, "FOOTER": { "text": "Support Team" }, "BUTTONS": [ { "type": "URL", "text": "View Details", "url": "https://example.com/details/{1}" }, { "type": "QUICK_REPLY", "text": "Contact Support" } ] } } ``` ### Required Parameters | Parameter | Type | Description | Example | | --------------------- | ------ | ------------------------------------------- | -------------------------- | | `company_id` | String | Unique company identifier | `"uuid-company"` | | `waba_id` | String | WhatsApp Business Account ID | `"123456789"` | | `name_template` | String | Template name (lowercase, underscores only) | `"order_confirmation"` | | `language_template` | String | Language code | `"es_ES"`, `"en_US"` | | `category_template` | String | Template category | `"MARKETING"`, `"UTILITY"` | | `components_template` | Object | Template components structure | See example above | ### Template Categories | Category | Description | Use Case | | ---------------- | ---------------------------------- | ------------------------ | | `MARKETING` | Promotional messages | Offers, announcements | | `UTILITY` | Transactional, functional messages | Order updates, alerts | | `AUTHENTICATION` | Security and verification messages | OTP codes, confirmations | ### Component Types #### HEADER * **format**: `"TEXT"`, `"IMAGE"`, `"VIDEO"`, `"DOCUMENT"` * **text**: Text content (only for TEXT format) * **example**: Example for variables #### BODY * **text**: Main message content * **Variables**: Use `{1}`, `{2}`, etc. for dynamic content (single braces in template creation) #### FOOTER * **text**: Footer text (static only, no variables) #### BUTTONS Array of button objects: **URL Button:** ```json theme={null} { "type": "URL", "text": "Button Text", "url": "https://example.com/{1}" } ``` **Quick Reply Button:** ```json theme={null} { "type": "QUICK_REPLY", "text": "Button Text" } ``` ### Response ```json theme={null} { "success": true, "message": "Template created successfully", "template": { "id": "new_template_id", "name": "nuevo_template", "status": "pending", "submitted_at": "2024-12-25T10:00:00Z" } } ``` ### Template Approval Process 1. **Submission**: Template is sent to Meta for review 2. **Review**: Meta reviews template (usually 24-48 hours) 3. **Approval/Rejection**: Template is approved or rejected with feedback 4. **Usage**: Approved templates can be used in notifications ### Template Status Values | Status | Description | | ---------- | ------------------------------------- | | `pending` | Submitted for review | | `approved` | Approved and ready to use | | `rejected` | Rejected (check feedback for reasons) | | `disabled` | Temporarily disabled | *** ## 📝 Template Best Practices ### Naming Conventions * Use lowercase letters only * Use underscores for spaces * Be descriptive: `welcome_new_user` vs `template1` * Include language if multiple: `welcome_es`, `welcome_en` ### Content Guidelines * **Be Clear**: Templates should be easily understood * **Variables**: Use numbered placeholders `{1}`, `{2}`, `{3}` (WhatsApp uses positional variables) * **Compliance**: Follow WhatsApp commerce policies * **Testing**: Test with real data before submission ### Common Rejection Reasons * ❌ Generic greetings ("Hello", "Hi there") * ❌ Promotional language in UTILITY templates * ❌ Missing context or unclear purpose * ❌ Incorrect variable usage * ❌ Policy violations (spam, inappropriate content) ### Approval Tips * ✅ Include specific business context * ✅ Use clear, professional language * ✅ Provide meaningful examples * ✅ Follow WhatsApp template guidelines * ✅ Test variable substitution thoroughly *** ## 🔧 Development Tips * Cache template information to reduce API calls * Monitor template status changes * Have fallback templates for rejected ones * Use consistent naming across your organization * Document templates for team reference # Voice Notifications API Source: https://docs.lovi.ai/en/api-reference/voice_notifications Official documentation for using Lovi's Voice API to send automated voice calls ## Introduction Voice notifications allow you to make automated calls with virtual agents. This endpoint enables you to send voice calls with AI-powered agents to your contacts, providing a personalized voice experience. ## 📞 Send Voice Notification To send a voice notification via the Lovi API, make a POST request to the voice endpoint with the necessary parameters and authentication. > **Method**: POST **Format**: JSON ### Endpoint ``` POST https://cloud.lovi.ai/functions/v1/notify/voice?access_key={YOUR_ACCESS_KEY} ``` ### Query Parameters | Parameter | Required | Description | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `access_key` | Yes | Your unique API access key. | | `unflatten` | No | If set to `true`, the body must contain flat variables (no nested objects). If set to `false` or omitted, the body can contain nested objects. | **Example URL:** ``` POST https://cloud.lovi.ai/functions/v1/notify/voice?access_key=your-api-key&unflatten=true ``` ### Headers | Key | Value | Required | Description | | ------------ | ---------------- | -------- | -------------------------------------------------- | | Content-Type | application/json | Yes | Indicates that the request body is in JSON format. | **Note**: Authentication is handled via the `access_key` parameter in the URL, not through headers. ### Body Request The body of the request contains the voice notification details. #### Structure with nested objects (unflatten=false or omitted) ```json theme={null} { "contact": { "number": "34666033135", "first_name": "María", "last_name": "González", "email": "maria@email.com", "titulacion": "Técnico en Enfermería", "fuente_cliente": "Web Principal" }, "recipient_id": "34911670470", "agent_id": "uuid-agente-voz", "notification_type": "marketing", "campaign_name": "Llamada Informativa Q4", "name_event": "info-curso-enfermeria", "datetime_sending": "2024-12-25T09:00:00", "timezone": "Europe/Madrid" } ``` #### Structure with flat variables (unflatten=true) ```json theme={null} { "contact.number": "34666033135", "contact.first_name": "María", "contact.last_name": "González", "contact.email": "maria@email.com", "contact.titulacion": "Técnico en Enfermería", "contact.fuente_cliente": "Web Principal", "recipient_id": "34911670470", "agent_id": "uuid-agente-voz", "notification_type": "marketing", "campaign_name": "Llamada Informativa Q4", "name_event": "info-curso-enfermeria", "datetime_sending": "2024-12-25T09:00:00", "timezone": "Europe/Madrid" } ``` ### Required Parameters | Parameter | Type | Description | Example | | ------------------- | ------ | ------------------------------------ | -------------------------------- | | `contact.number` | String | Phone number without '+' or spaces | `"34666033135"` | | `recipient_id` | String | ID or phone number of the recipient | `"34911670470"` | | `agent_id` | String | Unique identifier of the voice agent | `"uuid-agente-voz"` | | `notification_type` | String | Type for analytics | `"marketing"`, `"transactional"` | | `campaign_name` | String | Campaign identifier name | `"Voice Campaign Q4"` | ### Optional Parameters | Parameter | Type | Description | Example | Default | | -------------------- | -------- | ------------------------------ | ------------------------- | --------- | | `contact.first_name` | String | Contact's first name | `"María"` | - | | `contact.last_name` | String | Contact's last name | `"González"` | - | | `contact.email` | String | Contact's email | `"maria@email.com"` | - | | `name_event` | String | Event to trigger flows | `"info-curso-enfermeria"` | - | | `datetime_sending` | DateTime | Scheduled date/time (ISO 8601) | `"2024-12-25T09:00:00"` | Immediate | | `timezone` | String | Timezone for scheduling | `"Europe/Madrid"` | `"UTC"` | **Custom Contact Fields**: You can include any additional fields in the `contact` object for personalization (e.g., `titulacion`, `fuente_cliente`, etc.). ### Response On success (single `contact`): ```json theme={null} { "message": "Registration successful", "inquiry_set_id": "" } ``` The key needs the `voice_notifications` permission. The campaign id is `inquiry_set_id` (not `notification_id`). Poll progress with the same `GET /notify/status` as WhatsApp, passing `campaign_id` = that UUID. See the [WhatsApp Notifications API](./api_push_whatsapp_en). ### Voice-Specific Features * **AI-Powered Agents**: Use virtual agents with natural voice synthesis * **Personalization**: Include custom contact data for personalized calls * **Scheduling**: Program calls for specific times with timezone support * **Event Triggers**: Trigger specific conversation flows with `name_event` ### Best Practices * **Timing**: Schedule calls during business hours for better engagement * **Personalization**: Include relevant contact information for better conversation flow * **Agent Selection**: Choose appropriate agents based on use case (sales, support, etc.) * **Compliance**: Ensure calls comply with local regulations and consent requirements *** ## 📚 Tips for Voice Development * Always include `agent_id` for voice notifications * Use scheduling features to respect time zones * Test with development agents before production * Monitor call completion rates and adjust timing accordingly This API places **phone calls**. For voice on a website (microphone in the browser), see the [voice widget](/en/user-guide/Setting/ug_voicewidget_en). # Voice widget (embed) Source: https://docs.lovi.ai/en/api-reference/voice_widget What iawidget.js with mode=voice calls on cloud.lovi.ai. The host page does not call these itself. Install guide: [Voice widget](/en/user-guide/Setting/ug_voicewidget_en). You **do not call these from your site**. `https://widget.lovi.ai/iawidget.js` with `mode="voice"` loads an iframe that does. `customer` on the ` ``` | Attribute | Required | What it is | | --------------- | -------- | ----------------------------------------------------------------------------------------------------- | | `src` | Yes | `https://widget.lovi.ai/iawidget.js` | | `customer` | Yes | Company **access key** (the script sends it as `access_key`). Not the widget UUID. | | `widget` | Yes | Widget UUID. | | `mode` | Yes | Must be `voice`. Without it, the same script loads **text chat**. | | `language-lovi` | No | Language (`es`, `en`, …). If omitted, `navigator.language` (two letters). Do not use `language="en"`. | Loader order: **`mode="voice"` first**, then WhatsApp full, then chat. If both `mode="voice"` and WhatsApp full are set, **voice loads**. There is **no** `dom` embed for voice: the iframe is `position:fixed; bottom:0; left` or `right`. Chat `dom` / config WhatsApp-on-mobile do not apply to this iframe. No public npm package and no `window.startVoice`. The visitor taps the control. ## What the visitor sees 1. The voice control appears (fixed to the corner). 2. On a **real click**, the browser asks for the microphone (`getUserMedia` **inside** Lovi’s iframe, including `voiceIsolation` where supported). 3. They hear the agent and can hang up. 4. After hang-up they can leave 👍/👎 feedback. Test on **HTTPS** or `localhost`. The mic does not work on plain HTTP. If **your** page wraps the widget in a cross-origin iframe, that host iframe needs `allow="microphone"` and, where you use Permissions-Policy, allow `microphone` toward `https://widget.lovi.ai`. Lovi’s own iframe already sets `allow="microphone; autoplay"`. ## Appearance Colours, position, and animation come from `GET /functions/v1/voice-widget` (`Voice-config`). They are not set in your HTML except `language-lovi`. ## Audio and handoff (what the iframe does) You do **not** call these from the host page. The iframe does: 1. `GET …/functions/v1/voice-widget` — appearance (`Voice-config`) only. **Not** the live audio session. 2. `GET …/functions/v1/voice-widget/signed-url` — ElevenLabs ConvAI session (signed URL + SDK; WebSocket and/or WebRTC). 3. Also used internally: `POST …/voice-widget/search`, feedback, `POST …/voice-widget/handoff`, `POST …/voice-widget/handoff/end`, `GET …/voice-widget/chat-by-conversation`. There **is** a transfer-to-human path in the voice iframe (states such as `transferring` / `waiting_agent` / `with_human`, LiveKit). That is not a public API for your site’s JavaScript. ## Technical requirements (host page) The host must reach at least: * `https://widget.lovi.ai` * `https://cloud.lovi.ai` * `https://api.elevenlabs.io` and `wss://api.elevenlabs.io` * `wss://livekit.rtc.elevenlabs.io` * `https://cloud-api.livekit.io` * `https://cdn.jsdelivr.net` (AudioWorklet) Neither `iawidget.js` nor `widget.lovi.ai` send a `Content-Security-Policy` header. This list is for **your** CSP / firewall / tag manager. ## Troubleshooting | Problem | What to check | | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Nothing appears | Network: `iawidget.js` 200. Ad blockers. | | Console: `LOVI ERROR (voz): customer/widget no definidos` | Empty `customer` or `widget`. `customer` is the **access key**. | | Chat bubble instead of voice | `mode="voice"` missing. | | Microphone does not start | HTTPS/`localhost`, real click, mic permission, `allow="microphone"` on **your** wrapping iframe, Permissions-Policy to `widget.lovi.ai`. | | Script / audio / WebRTC blocked | Allowlist the hosts above, including `wss:` LiveKit and ElevenLabs. | ## Related * [Web Widget Configuration](./ug_webwidget_en) — text chat (omit `mode`). * [Phone numbers and voice AI](./voice-config) — calls to your company number. * [Voice widget API](/en/api-reference/voice_widget) — endpoints the iframe uses. * [Voice notifications API](/en/api-reference/voice_notifications) — outbound phone campaigns. # Web Widget Configuration Source: https://docs.lovi.ai/en/user-guide/Setting/ug_webwidget_en In this section, you’ll learn how to integrate and customize Lovi’s web widget to perfectly match the design and functionality of your website Web Widget List This is the bridge between your clever Agent and your website visitors. The **Web Widget** is that friendly little bubble in the corner of the screen that says "Hello!". Integrating it is a doddle: you just add a snippet of code to your site, and voilà! Your AI Agent is ready to chat 24/7 without you lifting a finger. ### 🎛️ Overview of Your Widgets This is your widget command centre. Here you can see every chat bubble you’ve created. * **Why have more than one?** You might want a "Sales Bot" for your pricing page and a "Tech Support" bot for your help centre. Here you manage them all. * **Key Actions per Widget:** * **📋 Copy Script:** Grab the magic code to paste onto your website. * **⚙️ Configure:** The fun part. Tweak colours, messages, and behaviour (we’ll get to this in a mo). * **🗑️ Delete:** Bin it if you don't need it. *** ### How to embed (chat) Paste this **before ``**. Do **not** set `mode="voice"` (that loads the [voice widget](./ug_voicewidget_en)). ```html theme={null} ``` Do not use `language="en"`. The loader reads **`language-lovi`**. If you omit it, it uses `navigator.language` (first two letters; `un` → `en`). | Attribute | Required | What it is | | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `src` | Yes | `https://widget.lovi.ai/iawidget.js` | | `customer` | Yes | Company **access key** (not a loose “company ID”). Same value the script sends as `access_key`. | | `widget` | Yes | Widget UUID. | | `language-lovi` | No | Forced language (`es`, `en`, …). | | `showClose` | No | Default `true`. `showClose="false"` opens chat full page and hides close. | | `dom` | No | HTML **element id** (`getElementById`), not a CSS selector (`#foo` / `.bar`). Embed runs if `dom` is set, or if config `is_fullscreen` is on **and** `element_dom` exists. If there is no node, there is no embed. | | `extra-context` | No | Comma-separated list → `context_extra` on the message. | | `lovi-source` | No | Stored in `info_user.source`. | | `whatsapp_type` / `whatsapp_number` | No | See WhatsApp below. | | `mode` | No | Omit it (or anything other than `voice`) for **text chat**. | Loader order: **`mode="voice"` → WhatsApp full → chat**. The `url` attribute on `