# Reaktly Docs — full documentation > Official documentation for Reaktly: set up the AI chat widget, connect your data sources, and integrate with the REST API. Canonical origin: https://docs.reaktly.com Structured index per page: https://docs.reaktly.com/llms.txt --- # Welcome to Reaktly Docs > Everything you need to get started with Reaktly — from onboarding to advanced integrations. URL: https://docs.reaktly.com/docs Welcome to the Reaktly documentation. Whether you are installing your first chat widget, connecting your data sources, or integrating over the API, you will find everything here. ## Browse by section - [Getting Started](/docs/getting-started): Set up your account and launch your first widget - [Platform Guide](/docs/platform): Operate the assistant: conversations, knowledge, analytics - [Integrations](/docs/integrations): Connect your CMS, shop, database, or custom source - [API Reference](/docs/api-reference): Ingestion API, Articles API, and authentication ## Popular topics | Task | Page | |---|---| | Install the chat widget | [Widget installation](/docs/getting-started/widget-installation) | | Push content via API | [Data ingestion guide](/docs/integrations/ingestion-api) | | Classify your content correctly | [Source types](/docs/integrations/source-types) | | Serve articles to your own help centre | [Articles API](/docs/api-reference/articles) | | Connect WhatsApp | [WhatsApp integration](/docs/integrations/whatsapp) | | Invite your team | [Team management](/docs/platform/team-management) | ## Use these docs with an AI assistant - **Ask AI** (bottom corner) answers from this documentation and cites the pages. - Every page has an **Open** menu with "Copy Markdown", "Open in ChatGPT", and "Open in Claude". - Machines can read [`/llms.txt`](/llms.txt) (index) and [`/llms-full.txt`](/llms-full.txt) (everything), or append `.mdx` to any page URL for its Markdown source. --- # Articles API > Serve published knowledge base articles to help centers, portals, and third-party apps. URL: https://docs.reaktly.com/docs/api-reference/articles ## Overview The Articles API exposes **published, public** articles from a knowledge base as JSON — for help centre widgets, customer portals, and integrations that render their own front end. - **Base URL:** `https://api.reaktly.com` - **Scope:** `articles:read` (see [Authentication](/docs/api-reference/authentication)) - Only articles with publish status `PUBLISHED` and visibility `PUBLIC` are returned. - `kbId` is the knowledge base's UUID. ```bash curl "https://api.reaktly.com/public/knowledge-bases/kb_xyz789/articles?page=1&pageSize=20" \ -H "x-api-key: YOUR_API_KEY" ``` ## Articles ### `GET /public/knowledge-bases/{kbId}/articles` Paginated list of published articles. | Query parameter | Type | Description | |---|---|---| | `channel` | string | Filter by delivery channel: `ALL`, `HELP_CENTER`, `WIDGET`, `PARTNER_PORTAL`, `INTERNAL`, `API` | | `categorySlug` / `categoryId` | string | Restrict to one category | | `featured` | boolean | Only featured articles | | `search` | string | Keyword search over title, excerpt and body | | `page` | number | 1-based page number (default `1`) | | `pageSize` | number | Articles per page (default `20`, max `100`) | ```json { "articles": [ { "id": "pub_123", "slug": "how-to-reset-your-password", "title": "How to reset your password", "contentType": "ARTICLE", "excerpt": "Reset your password from the sign-in screen…", "body": "Full article text…", "imageUrl": "https://cdn.example.com/cover.png", "category": { "id": "cat_1", "name": "Account", "slug": "account" }, "entryPoints": [ { "id": "ep_1", "question": "I forgot my password", "snippet": "Use the reset link…", "isPrimary": true, "tags": ["login"] } ], "readingTimeMinutes": 3, "publishedAt": "2026-08-01T09:00:00.000Z", "updatedAt": "2026-09-10T12:30:00.000Z", "helpfulness": { "helpfulCount": 42, "notHelpfulCount": 3 } } ], "total": 128, "page": 1, "pageSize": 20, "totalPages": 7 } ``` ### `GET /public/knowledge-bases/{kbId}/articles/by-slug/{slug}` A single article by its URL slug. Also increments the article's view count. Returns `404` when the article is not published or not public. ### `GET /public/knowledge-bases/{kbId}/articles/{publicationId}` A single article by its publication ID. Also increments the view count. ### `POST /public/knowledge-bases/{kbId}/articles/{publicationId}/feedback` Record whether a reader found the article helpful. ```json { "isHelpful": true } ``` Response `200`: ```json { "helpfulCount": 43, "notHelpfulCount": 3 } ``` ## Categories ### `GET /public/knowledge-bases/{kbId}/articles/categories` The category tree for navigation, with published article counts and nested children. ```json [ { "id": "cat_1", "name": "Account", "slug": "account", "description": null, "icon": null, "articleCount": 12, "children": [] } ] ``` ## Questions Entry-point questions are the fastest discovery path — visitors search a question and jump to the article that answers it. ### `GET /public/knowledge-bases/{kbId}/articles/questions/search` | Query parameter | Type | Description | |---|---|---| | `q` | string | Search text, e.g. `how do I reset my password` | | `limit` | number | Max results (default `10`, max `50`) | | `channel` | string | Filter by delivery channel | | `categorySlug` | string | Restrict to one category | Returns matching questions with their snippets. ### `GET /public/knowledge-bases/{kbId}/articles/questions/popular` Popular questions ranked by how often they are matched — useful for a help centre landing page or the widget's initial state. Takes the same `limit`, `channel` and `categorySlug` parameters. ### Question analytics Two endpoints feed the "popular questions" ranking. Both return `204 No Content`: | Endpoint | Call it when | |---|---| | `POST /public/knowledge-bases/{kbId}/articles/questions/{entryPointId}/matched` | The question appeared in your search results | | `POST /public/knowledge-bases/{kbId}/articles/questions/{entryPointId}/clicked` | A visitor clicked through to the article (also increments the article's view count) | ## Errors | Status | Meaning | |---|---| | `401` | Missing or invalid API key | | `403` | Key lacks `articles:read` | | `404` | Article not found, or not published and public | --- # Authentication > API key authentication, scopes, and security best practices. URL: https://docs.reaktly.com/docs/api-reference/authentication ## API keys All REST endpoints authenticate with an API key in the `x-api-key` header: ```bash curl https://api.reaktly.com/ingest \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ ... }' ``` Keys are created in the dashboard under **Settings → API keys**. The secret is shown once at creation — store it in your secret manager or environment, never in code. The dashboard keeps a prefix (e.g. `rk_live_abc…`) so you can tell keys apart later. > **Note:** > **API keys are not operator keys.** The `op_…` operator key in your website's widget snippet is public by design — it ships in page source and is protected by your allowed domains. API keys are secrets and belong on a server. ## Scopes A key can only do what its scopes allow — grant the minimum you need: | Scope | Dashboard label | Grants | |---|---|---| | `iq:import` | IQ Import | Push data into a knowledge base ([Ingestion API](/docs/api-reference/ingestion)) | | `iq:read` | IQ Read | Read knowledge base data | | `usage:read` | Usage Read | Read usage statistics and analytics | | `articles:read` | — | Read published articles ([Articles API](/docs/api-reference/articles)); provisioned by Reaktly | An endpoint returns `403` when the key is valid but lacks the required scope. ## Rotating a key 1. Create the replacement key with the same scopes. 2. Deploy the new value to your integration. 3. Verify traffic, then deactivate the old key in the dashboard. Keys can carry an expiry date; set one for keys used by contractors or temporary migrations. ## Security best practices - **Store keys in environment variables or a secret manager** — never in code, git, or client-side JavaScript. - **One key per integration** — a leaked key then has a small blast radius and a clear owner. - **Use the minimum scopes** — an ingestion key does not need `usage:read`. - **Set expiration dates** where possible, and rotate on a schedule. - **Never put an API key in a website** — browsers and mobile apps cannot keep secrets. The widget uses the public operator key instead. ## Errors | Status | Meaning | What to do | |---|---|---| | `401` | Missing or invalid API key | Check the header name (`x-api-key`) and that the key is active | | `403` | Valid key, insufficient scope or no access to the resource | Grant the scope, or check that the resource belongs to your tenant | | `429` | Rate limited | Back off and retry ([Error handling](/docs/integrations/error-handling)) | --- # API Reference > REST APIs for ingesting content and serving published articles. URL: https://docs.reaktly.com/docs/api-reference The Reaktly REST API speaks JSON over HTTPS and authenticates with API keys in the `x-api-key` header. ## Base URL ``` https://api.reaktly.com ``` ## Public APIs - [Authentication](/docs/api-reference/authentication): API keys, scopes, rotation, and security - [Ingestion API](/docs/api-reference/ingestion): Push content into a knowledge base - [Articles API](/docs/api-reference/articles): Serve published articles and questions | API | Scope | What it is for | |---|---|---| | [Ingestion API](/docs/api-reference/ingestion) | `iq:import` | Send content from your CMS, shop, or database into a knowledge base | | [Articles API](/docs/api-reference/articles) | `articles:read` | Read published articles, categories, and entry-point questions for your own help centre | | [Authentication](/docs/api-reference/authentication) | — | Creating keys, scopes, and error codes | ## Conventions - **Authentication** — every request carries `x-api-key`. The tenant is derived from the key, not from the request body. - **Ingestion is asynchronous** — an accepted item is queued for processing; `jobId`s let you correlate a submission with your own records. - **Backoff on `429`** — see [Error handling](/docs/integrations/error-handling). - **Errors** are standard HTTP status codes with a JSON body describing the problem (`400` invalid payload, `401` missing key, `403` missing scope, `404` unknown resource). ## Not in the public API Dashboard features — conversations, analytics, widget configuration, team management — are served by private session-authenticated endpoints. They are not documented here and are not covered by API keys. The API-key scope model already names reserved scopes for future public surface (`iq:read`, `iq:embed`, `iq:enrich`, `iq:diagnostics`). They stay inert until the corresponding endpoints ship. --- # Ingestion API > REST endpoints for pushing content into a Reaktly knowledge base. URL: https://docs.reaktly.com/docs/api-reference/ingestion ## Base URL ``` https://api.reaktly.com ``` Every request needs an API key with the `iq:import` scope (see [Authentication](/docs/api-reference/authentication)): ```bash curl https://api.reaktly.com/ingest \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ ... }' ``` Ingestion is asynchronous: an accepted item is queued, processed (normalized, chunked, embedded) and then searchable by the AI. `jobId`s are derived from `integrationId` + `externalId`, so re-submitting the same pair does not create duplicate work. ## POST /ingest — single item ### Request body | Field | Type | Required | Description | |---|---|---|---| | `integrationId` | string | Yes | Your integration's ID (the connector this data belongs to) | | `knowledgeBaseId` | string | No | Target knowledge base. Omit to use the tenant's default | | `sourceType` | string | Yes | Content kind — see [Source types](/docs/integrations/source-types) | | `externalId` | string | Yes | Stable ID from your system; the deduplication key within the integration | | `sourceOrigin` | string | No | Origin tag (e.g. `wordpress-rest`). Scopes [cleanup](#post-ingestcleanup--remove-orphaned-items) to this connector | | `data.title` | string | Yes | Item title | | `data.content` | string | Yes | The text the AI answers from — this is what gets embedded | | `data.url` | string | No | Canonical URL of the original item, used in citations | | `data.metadata` | object | No | Original raw data (price, sku, author, …) | | `data.sourceData` | object | No | Structured source data for richer answers | | `data.hash` | string | No | Content hash — unchanged hashes skip re-processing ([Change detection](/docs/integrations/change-detection)) | | `options.forceRefresh` | boolean | No | Re-process even if the hash is unchanged | | `options.priority` | `"high"` \| `"low"` | No | Queue priority. Use `high` for user-triggered imports, `low` for background syncs | | `options.metadataOnly` | boolean | No | Update `url`, `metadata` and `sourceData` without re-embedding — for price or URL changes | ### Example ```json { "integrationId": "int_abc123", "knowledgeBaseId": "kb_xyz789", "sourceType": "ARTICLE", "externalId": "blog-post-42", "sourceOrigin": "my-cms", "data": { "title": "How to use our product", "content": "This guide walks you through...", "url": "https://your-site.com/blog/how-to-use", "hash": "a1b2c3d4e5f6" }, "options": { "priority": "high" } } ``` ### Response `200` ```json { "status": "queued", "jobId": "int_abc123-blog-post-42" } ``` ## POST /ingest/bulk — batch import Optimised for initial loads. Each item has the shape above; the tenant is taken from the API key. ```json { "items": [ { "integrationId": "int_abc123", "sourceType": "PRODUCT", "externalId": "sku-1", "data": { "title": "…", "content": "…" } }, { "integrationId": "int_abc123", "sourceType": "PRODUCT", "externalId": "sku-2", "data": { "title": "…", "content": "…" } } ] } ``` ### Response `200` ```json { "status": "queued", "jobCount": 50, "estimatedTime": "5s", "jobIds": ["int_abc123-sku-1", "int_abc123-sku-2"] } ``` > **Note:** > `estimatedTime` is the queue's own estimate (10 items per second). It describes queueing, not embedding time. ## POST /ingest/cleanup — remove orphaned items After a full sync, tell Reaktly which `externalId`s still exist. Sources with the same `sourceOrigin` that are **not** in the keep list are deleted — so items removed from your CMS disappear from the knowledge base too. ```json { "knowledgeBaseId": "kb_xyz789", "sourceOrigin": "my-cms", "keepExternalIds": ["blog-post-42", "blog-post-43"] } ``` ### Response `200` ```json { "deleted": 3, "orphanedExternalIds": ["blog-post-17", "blog-post-18", "blog-post-19"] } ``` Cleaning up is scoped: only sources whose `sourceOrigin` matches are considered. An empty `keepExternalIds` list deletes every source from that origin — use it with care. ## Errors See [Error handling](/docs/integrations/error-handling) for status codes, retry and backoff. ## Where to go next - [Data ingestion guide](/docs/integrations/ingestion-api) — worked examples in TypeScript, Python and cURL - [Source types](/docs/integrations/source-types) — how to classify what you send - [Change detection](/docs/integrations/change-detection) — hashing to skip unchanged content --- # Account Setup > Create your Reaktly account and configure your workspace. URL: https://docs.reaktly.com/docs/getting-started/account-setup ## Create Your Account Visit [app.reaktly.com](https://app.reaktly.com) and sign up with your email or SSO provider. ## Configure Your Workspace After signing in, you'll be guided through the initial workspace setup: 1. **Organization name** — This identifies your company 2. **Widget branding** — Upload your logo and set your brand colors 3. **Team invitations** — Invite your colleagues (optional, can be done later) ## Next Steps Once your workspace is ready, proceed to [install the chat widget](/docs/getting-started/widget-installation) on your site. --- # Your First Conversation > Test your AI assistant and see it answer questions from your knowledge base. URL: https://docs.reaktly.com/docs/getting-started/first-conversation ## Testing the widget Once the widget is installed and your knowledge base has content: 1. Open your website and wait for the launcher to appear. 2. Open the chat and ask a question your content answers — not one you know it doesn't. 3. The AI searches your knowledge base and answers with the sources it used. Start with a question a customer would actually ask ("Do you ship to Austria?", "How do I cancel?"), not a keyword you know is in a document. That is the behaviour you are testing. ## What to expect - **Grounded answers** — responses come from your content, not the model's general knowledge - **Source citations** — the answer links to the knowledge base items behind it - **Follow-ups** — visitors can keep asking in the same conversation - **A fixed AI disclosure** — the widget labels AI-generated answers as such; this notice is part of the product, not a setting ## If answers are wrong or missing | Symptom | Likely cause | What to do | |---|---|---| | "I don't know" for something you have content about | Content not ingested, or still processing | Check the item exists in the knowledge base; allow a few minutes after ingestion | | Outdated answer | Stale content | Update the source item (see [Change detection](/docs/integrations/change-detection) for automated syncs) | | Too generic | The content is thin or the question is broad | Add detail to the source item; the AI answers from what you publish | | Wrong item cited | Overlapping content | Make each item answer one topic clearly; use [source types](/docs/integrations/source-types) to separate concerns | ## Next steps - [Platform guide](/docs/platform) — dashboard, conversations, analytics - [Connect more data sources](/docs/integrations) - [Customize the widget](/docs/platform/widget-customization) — colours, copy, behaviour --- # Getting Started > Get up and running with Reaktly in minutes. URL: https://docs.reaktly.com/docs/getting-started This section takes you from zero to a live AI assistant on your site. ## Setup checklist 1. **Create your account** — sign up and set up your workspace 2. **Install the widget** — one snippet on your site 3. **Build your knowledge base** — import the content the AI answers from 4. **Go live** — test and launch - [Account Setup](/docs/getting-started/account-setup): Create your workspace and invite your team - [Widget Installation](/docs/getting-started/widget-installation): Embed the widget and verify it works - [Knowledge Base Basics](/docs/getting-started/knowledge-base-basics): How the AI answers from your content - [Your First Conversation](/docs/getting-started/first-conversation): Test answers against your content ## After the basics - [Connect your data sources](/docs/integrations) for automatic content sync - [Customize the widget](/docs/platform/widget-customization) to match your brand - [Read the API reference](/docs/api-reference) for programmatic access --- # Knowledge Base Basics > Learn how the Reaktly knowledge base powers AI-driven answers. URL: https://docs.reaktly.com/docs/getting-started/knowledge-base-basics ## What Is the Knowledge Base? The knowledge base is the foundation of Reaktly IQ. It stores your content — articles, product info, FAQs, documentation — as vector embeddings that the AI uses to answer visitor questions accurately. ## How It Works 1. **You provide content** — Import from your CMS, database, or paste manually 2. **We process it** — Content is chunked, embedded, and indexed 3. **AI answers questions** — When a visitor asks something, the AI searches your knowledge base and generates an accurate response ## Adding Content There are several ways to populate your knowledge base: - **Manual entry** — Add items directly in the dashboard - **Ingestion API** — Push content programmatically from any source - **Built-in connectors** — Use pre-built integrations (Shopify, WordPress, etc.) See [Integrations](/docs/integrations) for detailed guides on connecting your data sources. ## Best Practices - **Keep content up to date** — Stale content leads to inaccurate answers - **Be specific** — Detailed content produces better AI responses - **Use source types** — Categorize items (PRODUCT, ARTICLE, SERVICE, etc.) for better context - **Monitor conversations** — Review AI responses to identify knowledge gaps --- # Widget Installation > Add the Reaktly chat widget to any website — script tag, deferred loader, framework notes, and verification. URL: https://docs.reaktly.com/docs/getting-started/widget-installation ## Prerequisites - A Reaktly operator with a widget configuration - Your **operator key** (`op_…`) — reveal it in the dashboard under the widget's **Installation** tab (**Reveal key**) - The domains that may embed the widget, listed under **Allowed domains** (see [Restrict embedding](#restrict-embedding)) ## 1. Embed the widget Paste this snippet into your site, just before the closing `` tag: ```html ``` The bundle is a side-effecting ES module: importing it registers the global `window.solis(...)` command. `init` fetches your widget configuration and renders the launcher. > **Note:** > The dashboard shows the same snippet with your operator key filled in — copy it from the widget's **Installation** tab. ### Optional init options | Option | Type | Description | |---|---|---| | `operatorKey` | string | **Required.** Your operator's public key (`op_…`) | | `locale` | string | Force the widget language, e.g. `"de"`. Omit to let the widget use the visitor's browser language | | `contact.userId` | string | Your identifier for the visitor — lets the AI and your operators address them consistently | | `contact.email` | string | Visitor email | | `contact.name` | string | Visitor name | | `showLauncher` | boolean | Set `false` when your own button should open the chat ([client-owned trigger](#client-owned-trigger)) | | `panelId` | string | `id` for the widget element, so your own trigger can point at it with `aria-controls` | ## 2. Verify the installation 1. Load the page in a browser and wait for the launcher to appear in the bottom corner. 2. Open the chat and send a message — it should be answered from your knowledge base. 3. Watch the browser console: the widget reports a missing or rejected operator key there instead of failing silently. ## Deferred loading (recommended for content sites) The bundle is ~150 kB gzipped. The **loader** is the small alternative: it installs `window.solis` as a queue (about 1 kB) and fetches the bundle only when the first visitor actually interacts with the chat. ```html ``` - Any element with a `data-solis-*` attribute becomes a trigger — no inline JavaScript, which makes this work in CMS-built headers. - The first click waits for the download. To warm the bundle up in the background instead, add `data-idle="3000"` (milliseconds) or `data-preload="eager"`. - The loader state is observable on the document element: `` is `idle`, `loading` or `loaded`. ## Pin a version in production `latest/` always serves the newest release — convenient while evaluating, but a moving target. For production, pin the bundle and verify its integrity: ```html ``` The hashes come from `…/v1.26.0/integrity.json` (one `sha384` per shipped file). `crossorigin="anonymous"` is required — without it the browser does not check the hash. A pinned version is never overwritten, so rolling back means pointing at the previous version. ## Framework notes ### Next.js / React Import the bundle in a client component and initialise it once: ```tsx "use client"; import { useEffect } from "react"; export function ReaktlyWidget({ operatorKey }: { operatorKey: string }) { useEffect(() => { let active = true; import( "https://storage.googleapis.com/reaktly-solis-cdn/latest/solis-widget.js" ).then(() => { if (!active) return; window.solis("init", { operatorKey }); }); return () => { active = false; window.solis("destroy"); }; }, [operatorKey]); return null; } ``` A React component package is on the roadmap; until it ships, the effect above is the supported integration. ### WordPress Add the snippet from step 1 to your theme's `footer.php`, or use a "insert headers and footers" plugin. A dedicated Reaktly plugin is on the roadmap. ### Plain HTML The snippet from step 1 is all you need — it works on static sites and any server-rendered stack. ## Control the widget from your page Every command is a `window.solis(...)` call: | Command | Effect | |---|---| | `window.solis('open')` / `('close')` / `('toggle')` | Open, close, or toggle the chat window | | `window.solis('show')` / `('hide')` | Show or hide the launcher (e.g. on checkout pages) | | `window.solis('update', options)` | Update the configuration without a reload | | `window.solis('destroy')` | Remove the widget and its listeners | ### Events Subscribe with `window.solis('on', event, handler)` and unsubscribe with `off`: | Event | Fires when | |---|---| | `ready` | The widget finished initialising | | `open` / `close` | The chat window opened or closed | | `message:sent` / `message:received` | A message left or arrived | | `error` | The widget hit an error | Every event is also dispatched as a DOM event (`solis:open`, `solis:close`, …) on `window`, `document`, and the widget element — so plain HTML pages can react without the JavaScript API. ### Client-owned trigger When your own button should open the chat, switch off the launcher and drive the widget yourself: ```html ``` Focus is handled for you: the widget remembers what opened it and returns focus there on close. Set `panelId` if you want `aria-controls` on your trigger, and reflect `aria-expanded` from the `solis:open` / `solis:close` events. ## Restrict embedding **Allowed domains** in the widget settings restrict which origins may use your widget (OriginGuard). One pattern per line: | Pattern | Matches | |---|---| | `example.com` | Exact domain | | `*.example.com` | All subdomains | | `https://app.example.com` | Specific protocol and domain | Leaving the list empty allows every domain. Once you list domains, only matching origins are accepted. ## Browser support | Engine | Minimum | |---|---| | Chrome / Edge | 111 | | Safari | 16.2 | | Firefox | 113 | The floor is set by `color-mix()` in the widget's design tokens. There is no transpiled build — below the floor the widget renders nothing rather than breaking the layout. ## Troubleshooting | Symptom | Check | |---|---| | Nothing renders | Console for an operator-key error; the key was revealed for the correct operator | | Widget renders, but requests are rejected | The page's origin is missing from **Allowed domains** | | Launcher missing, chat opens via your button | Expected when `showLauncher: false` | | Chat opens but stays empty | The operator's widget configuration failed to load — check the network tab for the config request | ## Next steps - [Your First Conversation](/docs/getting-started/first-conversation) — test the assistant against your content - [Widget Customization](/docs/platform/widget-customization) — colors, copy, and behaviour - [Knowledge Base Basics](/docs/getting-started/knowledge-base-basics) — what the AI answers from --- # Best Practices > Guidelines for building reliable, high-quality integrations. URL: https://docs.reaktly.com/docs/integrations/best-practices ## Security - **Never hardcode API keys** — Use environment variables - **Use one API key per connector** — Easier to revoke and audit - **Rotate keys regularly** — Set expiration dates on API keys ## Content Quality - **Write for AI** — Clear, descriptive content produces better answers - **Include context** — Don't just provide titles; include full descriptions - **Use meaningful external IDs** — Makes debugging and deduplication easier - **Set source types correctly** — Helps the AI provide contextual responses ## Performance - **Use bulk endpoints** for initial loads (batches of 50-100 items) - **Include hash values** to skip unchanged content - **Use `low` priority** for background syncs, `high` for user-triggered imports - **Implement retry logic** with exponential backoff ## Monitoring - **Log sync results** — Track success/failure rates - **Monitor knowledge base** — Check that content appears after ingestion - **Review AI responses** — Ensure ingested content improves answer quality - **Set up alerts** — Notify your team if sync jobs fail repeatedly --- # Change Detection > Use content hashing to avoid re-processing unchanged items. URL: https://docs.reaktly.com/docs/integrations/change-detection ## How It Works When you include a `hash` field in your ingestion payload, Reaktly compares it against the previously stored hash. If they match, the item is skipped — saving processing time and embedding costs. ## Generating a Hash Create a SHA-256 hash of the content fields that matter: ```ts import crypto from 'crypto'; function computeHash(item: { title: string; content: string }): string { return crypto .createHash('sha256') .update(JSON.stringify({ title: item.title, content: item.content })) .digest('hex'); } ``` ## Force Refresh To bypass hash checking and force re-processing: ```json { "options": { "forceRefresh": true } } ``` Use this when you've changed your embedding model or want to re-process all content. --- # Build a Custom Connector > Step-by-step guide to building your own Reaktly data connector. URL: https://docs.reaktly.com/docs/integrations/custom-connector ## Overview You can push data from any source into Reaktly using the Ingestion API. This guide shows you how to build a connector for your specific data source. ## Prerequisites Before you start, you'll need: - [ ] A Reaktly API key with `iq:import` scope - [ ] Your Integration ID (from Settings → Integrations) - [ ] Your Knowledge Base ID (from Settings → Knowledge Base) - [ ] Access to your data source ## Architecture A typical connector follows this pattern: 1. **Extract** — Read data from your source 2. **Transform** — Map fields to the Reaktly ingestion format 3. **Load** — Push to the Ingestion API ## Step-by-Step ### 1. Set Up Authentication ```ts const config = { apiKey: process.env.REAKTLY_API_KEY, baseUrl: 'https://api.reaktly.com', integrationId: process.env.REAKTLY_INTEGRATION_ID, knowledgeBaseId: process.env.REAKTLY_KB_ID, }; ``` ### 2. Build Your Transformer Map your data to the Reaktly format: ```ts function transform(item: YourDataType) { return { integrationId: config.integrationId, knowledgeBaseId: config.knowledgeBaseId, sourceType: 'ARTICLE', // or PRODUCT, SERVICE, PERSON, PAGE externalId: `your-source-${item.id}`, data: { title: item.title, content: item.body, // The text the AI will use url: `https://your-site.com/items/${item.slug}`, sourceData: { // Structured data for rich responses author: item.author, category: item.category, }, hash: computeHash(item), // For change detection }, }; } ``` ### 3. Push to Reaktly ```ts async function sync(items: YourDataType[]) { const transformed = items.map(transform); const response = await fetch(`${config.baseUrl}/ingest/bulk`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': config.apiKey, }, body: JSON.stringify({ items: transformed }), }); const result = await response.json(); console.log(`Synced ${result.jobCount} items`); } ``` ## Full Reference - [Ingestion API reference](/docs/api-reference/ingestion) — endpoints, fields, responses - [Data ingestion guide](/docs/integrations/ingestion-api) — language examples and batching - [Source types](/docs/integrations/source-types) — how to classify content - [Integration patterns](/docs/integrations/integration-patterns) — webhook vs. scheduled syncs - [Error handling](/docs/integrations/error-handling) — retries and backoff --- # Error Handling > Handle errors and implement retry logic for reliable ingestion. URL: https://docs.reaktly.com/docs/integrations/error-handling ## HTTP Status Codes | Code | Meaning | Action | |---|---|---| | `200` | Success — item queued | ✅ Continue | | `400` | Bad request — validation error | Fix the payload | | `401` | Unauthorized — invalid API key | Check your API key | | `403` | Forbidden — insufficient scopes or expired key | Verify key has `iq:import` scope | | `413` | Payload too large | Reduce batch size or content length | | `429` | Rate limited | Back off and retry | | `500` | Server error | Retry with exponential backoff | ## Retry Strategy Implement exponential backoff with jitter for resilient integrations: ```ts async function ingestWithRetry(payload: any, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch('https://api.reaktly.com/ingest', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.REAKTLY_API_KEY!, }, body: JSON.stringify(payload), }); if (response.ok) return response.json(); const isRetryable = [429, 500, 502, 503].includes(response.status); if (!isRetryable || attempt === maxRetries) { throw new Error(`Ingestion failed: ${response.status}`); } const delay = Math.min(1000 * 2 ** attempt, 30000); const jitter = Math.random() * 1000; await new Promise(r => setTimeout(r, delay + jitter)); } } ``` --- # Integrations > Connect your CMS, shop, or database to a Reaktly knowledge base. URL: https://docs.reaktly.com/docs/integrations Reaktly answers from your content. Anything you can export as text can be ingested — from a CMS, an e-commerce platform, a database, or a custom application. ## How integration works 1. **Create an API key** with the `iq:import` scope ([Authentication](/docs/api-reference/authentication)) 2. **Push your data** — via a built-in connector or the Ingestion API 3. **Reaktly processes it** — normalization, chunking, embedding, indexing 4. **The AI answers** from it, with citations ## Choose your path - [Data ingestion guide](/docs/integrations/ingestion-api): First request, batching, and keeping in sync - [Shopify](/docs/integrations/shopify): Sync products, prices, and stock - [WordPress](/docs/integrations/wordpress): Import posts and pages - [Payload CMS](/docs/integrations/payload-cms): Push on publish with an afterChange hook - [WhatsApp](/docs/integrations/whatsapp): Answer on WhatsApp Business - [Build a custom connector](/docs/integrations/custom-connector): End-to-end walkthrough for your own source ## Quick start The fastest way to see something in the knowledge base: ```bash curl -X POST https://api.reaktly.com/ingest \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "integrationId": "your-integration-id", "sourceType": "ARTICLE", "externalId": "my-first-article", "data": { "title": "Getting Started Guide", "content": "The text the AI will answer from…" } }' ``` Then read [Source types](/docs/integrations/source-types) to classify what you send, and [Integration patterns](/docs/integrations/integration-patterns) to pick a sync strategy. --- # Data Ingestion Guide > Worked examples for pushing content into Reaktly from TypeScript, Python, and cURL. URL: https://docs.reaktly.com/docs/integrations/ingestion-api This guide gets data into a knowledge base. For the exact field list and every endpoint, see the [Ingestion API reference](/docs/api-reference/ingestion). ## What you need | Value | Where to find it | |---|---| | API key with `iq:import` | Dashboard → **Settings → API keys** ([Authentication](/docs/api-reference/authentication)) | | `integrationId` | Dashboard → the integration (connector) the data belongs to | | `knowledgeBaseId` | Dashboard → Knowledge base. Optional — omit to use the tenant's default | ## The flow 1. **Extract** — read items from your source system 2. **Transform** — map to the ingestion shape: `sourceType`, `externalId`, `data.title`, `data.content` 3. **Load** — `POST /ingest` for one item, `POST /ingest/bulk` for batches 4. **Clean up** — after a full sync, `POST /ingest/cleanup` removes items deleted at the source ## First request (cURL) ```bash curl -X POST https://api.reaktly.com/ingest \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "integrationId": "your-integration-id", "knowledgeBaseId": "your-kb-id", "sourceType": "ARTICLE", "externalId": "my-first-article", "data": { "title": "Getting Started Guide", "content": "The text the AI will answer from…", "url": "https://your-site.com/getting-started" } }' ``` A `200` with `{ "status": "queued", "jobId": "…" }` means the item is accepted. Processing (chunking, embedding, indexing) happens in the background — allow a short delay before the assistant can answer from it. ## TypeScript ```ts const response = await fetch('https://api.reaktly.com/ingest', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.REAKTLY_API_KEY!, }, body: JSON.stringify({ integrationId: process.env.REAKTLY_INTEGRATION_ID, sourceType: 'ARTICLE', externalId: `post-${post.id}`, data: { title: post.title, content: post.body, url: `https://your-site.com/blog/${post.slug}`, hash: contentHash(post), }, options: { priority: 'high' }, }), }); if (!response.ok) throw new Error(`Ingestion failed: ${response.status}`); const result = await response.json(); console.log('Queued as', result.jobId); ``` ## Python ```python import os import requests response = requests.post( "https://api.reaktly.com/ingest", json={ "integrationId": os.environ["REAKTLY_INTEGRATION_ID"], "sourceType": "ARTICLE", "externalId": f"post-{post_id}", "data": { "title": title, "content": body, "url": url, }, }, headers={"x-api-key": os.environ["REAKTLY_API_KEY"]}, timeout=30, ) response.raise_for_status() print(response.json()["jobId"]) ``` ## Batching an initial load Send 50–100 items per request with `/ingest/bulk`. Keep `externalId`s stable across syncs — they are the deduplication key, and re-sending the same item updates it rather than creating a duplicate. ```ts const chunk = items.slice(0, 100).map(toIngestionItem); await fetch('https://api.reaktly.com/ingest/bulk', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.REAKTLY_API_KEY!, }, body: JSON.stringify({ items: chunk }), }); ``` ## Keeping in sync | Goal | Approach | |---|---| | Skip unchanged content | Send `data.hash`; unchanged items are skipped ([Change detection](/docs/integrations/change-detection)) | | Update prices or URLs only | `options.metadataOnly: true` updates metadata without re-embedding | | Remove deleted items | `POST /ingest/cleanup` with the full list of surviving `externalId`s | | Prioritise user-triggered imports | `options.priority: "high"` | ## Next steps - [Ingestion API reference](/docs/api-reference/ingestion) — endpoints, fields, responses - [Integration patterns](/docs/integrations/integration-patterns) — webhook vs. scheduled syncs - [Error handling](/docs/integrations/error-handling) — retries and backoff - [Build a custom connector](/docs/integrations/custom-connector) — a full walkthrough --- # Integration Patterns > Choose the right sync strategy for your use case. URL: https://docs.reaktly.com/docs/integrations/integration-patterns ## Patterns Overview | Pattern | Best For | Complexity | |---|---|---| | **One-time bulk sync** | Initial data import | Low | | **Webhook-driven** | Real-time updates from CMS/e-commerce | Medium | | **Scheduled batch sync** | Periodic database exports | Medium | | **Real-time push** | ORM hooks, CMS afterChange hooks | Low-Medium | ## One-Time Bulk Sync Use the bulk endpoint for initial data loads: ```ts const items = await fetchAllFromDatabase(); const batches = chunk(items, 50); // Split into batches of 50 for (const batch of batches) { await fetch('https://api.reaktly.com/ingest/bulk', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ items: batch }), }); } ``` ## Webhook-Driven Set up webhooks in your CMS to push updates on content changes. The data flows in real-time without polling. ## Scheduled Batch Sync Run a cron job to sync changes periodically: ```bash # Every 6 hours 0 */6 * * * /usr/bin/node /path/to/sync-script.js ``` ## Real-Time Push (Hooks) Use ORM or CMS hooks to push on every save. Best for frameworks like Payload CMS, Strapi, or Prisma. See [Payload CMS Integration](/docs/integrations/payload-cms) and [Custom Connector](/docs/integrations/custom-connector) for examples. --- # Payload CMS Integration > Connect your Payload CMS content to Reaktly. URL: https://docs.reaktly.com/docs/integrations/payload-cms ## Overview If you're using Payload CMS, you can push your content to Reaktly using afterChange hooks or a sync script. ## Using Payload Hooks Add an `afterChange` hook to your collections to push content on every publish: ```typescript import type { CollectionAfterChangeHook } from 'payload'; import crypto from 'crypto'; const REAKTLY_API = process.env.REAKTLY_API_URL; const API_KEY = process.env.REAKTLY_API_KEY; const INTEGRATION_ID = process.env.REAKTLY_INTEGRATION_ID; const KB_ID = process.env.REAKTLY_KB_ID; export const syncToReaktly: CollectionAfterChangeHook = async ({ doc, collection }) => { if (doc._status === 'draft') return doc; const content = typeof doc.content === 'string' ? doc.content : JSON.stringify(doc.content); try { await fetch(`${REAKTLY_API}/ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ integrationId: INTEGRATION_ID, knowledgeBaseId: KB_ID, sourceType: 'ARTICLE', externalId: `payload-${collection.slug}-${doc.id}`, data: { title: doc.title, content, url: `https://your-site.com/${collection.slug}/${doc.slug}`, hash: crypto.createHash('sha256').update(content).digest('hex'), }, }), }); } catch (error) { console.error('Failed to sync to Reaktly:', error); } return doc; }; ``` ## Full Sync Script For initial data import, run a one-time sync using Payload's Local API. See our [Custom Connector guide](/docs/integrations/custom-connector) for the full pattern. --- # Shopify Integration > Sync your Shopify product catalog into a Reaktly knowledge base. URL: https://docs.reaktly.com/docs/integrations/shopify ## Overview The Shopify integration imports your product catalog and keeps it up to date, so the AI answers questions about products, pricing, and availability from live data. ## Setup 1. Install the Reaktly app on your Shopify store (Reaktly provides the install link; the app requests product read access). 2. Authorize the connection — Shopify redirects back to Reaktly with an access token. 3. Reaktly imports your catalog into the selected knowledge base. 4. Product changes arrive through webhooks; the knowledge base follows within minutes. No manual ingestion code is involved — Shopify events land on Reaktly's webhook endpoint. ## What gets synced | Field | How the AI uses it | |---|---| | Title | Answers "do you have …?" | | Description | Product details, cleaned of HTML | | Vendor | Brand questions | | Tags | Category and use-case questions | | Price | Price questions | | SKU | Exact product identification | | Inventory (`inStock`, `quantityAvailable`) | Availability answers | | Images | Product visuals in answers | Behind the scenes each product is ingested as a `PRODUCT` source with this data in `data.sourceData` — the same shape you would send yourself via the [Ingestion API](/docs/api-reference/ingestion). ## Keeping the catalog in sync The integration reacts to Shopify product events (`products/create`, `products/update`) and re-ingests the affected product. Items deleted in Shopify are removed from the knowledge base by the sync's cleanup step. ## Troubleshooting | Symptom | Check | |---|---| | Products missing | The product is published to the sales channel the app can read | | Stale price or stock | The product update webhook arrived — check the integration's recent activity | | Wrong answers about variants | Put variant-level detail into the product description; the first variant's price and the summed inventory are what the catalog sync captures | --- # Source Types > Classify ingested content so the AI answers with the right context. URL: https://docs.reaktly.com/docs/integrations/source-types ## Available source types Every ingested item carries a `sourceType`. It tells the platform how to treat the content and which structured fields the AI can use in answers. | Source type | Use for | Typical structured fields | |---|---|---| | `PRODUCT` | Products, SKUs, shop items | `sku`, `price`, `currency`, `vendor`, `inStock` | | `ARTICLE` | Blog posts, news, guides | `author`, `publishedAt`, `category`, `tags` | | `SERVICE` | Service offerings | `price`, `duration`, `category` | | `PERSON` | Team members, experts, agents | `role`, `department`, `expertise` | | `BLOG_POST` | Blog content with post semantics | `author`, `publishedAt` | | `DOCUMENTATION` | Product or technical docs | `section`, `version` | | `PDF` / `FILE` | Uploaded documents | file metadata | | `VIDEO` / `AUDIO` | Reserved for transcript ingestion | — | Put structured values in `data.sourceData` (or `data.metadata` for the raw original record). The AI sees them alongside the text, which is what makes answers like "is this in stock?" possible. ## Choosing a type - Selling products → `PRODUCT` - Publishing content on a blog → `ARTICLE` (or `BLOG_POST` when post semantics matter) - Describing what you offer → `SERVICE` - Introducing people → `PERSON` - Technical or product documentation → `DOCUMENTATION` - Anything uploaded as a file → `PDF` / `FILE` > **Note:** > There is no generic `PAGE` type. For website pages, use the type that reflects the *content*: an article is `ARTICLE`, a product page is `PRODUCT`, documentation is `DOCUMENTATION`. ## Examples ### PRODUCT ```json { "sourceType": "PRODUCT", "externalId": "prod-123", "data": { "title": "Premium Headphones", "content": "Wireless noise-cancelling headphones with 30-hour battery life.", "url": "https://shop.example.com/headphones", "sourceData": { "sku": "HP-NC-001", "price": 299.99, "currency": "EUR", "vendor": "AudioTech", "inStock": true } } } ``` ### ARTICLE ```json { "sourceType": "ARTICLE", "externalId": "post-456", "data": { "title": "10 Tips for Better Customer Support", "content": "Full article text here…", "url": "https://blog.example.com/support-tips", "sourceData": { "author": "Jane Smith", "publishedAt": "2026-01-15", "category": "Support", "tags": ["customer-service", "best-practices"] } } } ``` Both examples are complete requests when combined with `integrationId` — see the [Ingestion API reference](/docs/api-reference/ingestion). --- # WhatsApp Integration > Connect WhatsApp Business to Reaktly for AI-powered customer conversations on WhatsApp. URL: https://docs.reaktly.com/docs/integrations/whatsapp ## Overview The Reaktly WhatsApp integration connects your WhatsApp Business number to your AI-powered operator. Incoming WhatsApp messages are routed to Reaktly, where your AI responds instantly — and can hand off to a human agent when needed. Key capabilities include text messaging, interactive button replies, AI-powered responses drawn from your knowledge base, automatic conversation routing, and seamless human handoff. This integration uses the **WhatsApp Business API** (via Meta's Cloud API) — it does not work with personal WhatsApp or the WhatsApp Business mobile app. > **Note:** > You need access to the **WhatsApp Business API** through a Meta App. A regular WhatsApp account is not sufficient. ## Prerequisites Before you begin, make sure you have: - A **Meta Business Account** (verified, or with verification in progress) - A **Facebook Developer Account** at [developers.facebook.com](https://developers.facebook.com) - A **phone number** for WhatsApp Business — you can use Meta's test number during development - An active **Reaktly account** with at least one operator configured - An **HTTPS-accessible API endpoint** for webhook delivery (your Reaktly instance) ## Step 1: Create a Meta App 1. Go to [developers.facebook.com](https://developers.facebook.com) → **My Apps** → **Create App**. 2. Select the app type **Business**. 3. Fill in your app name, contact email, and select your Business Account. 4. On the app dashboard, click **Add Product** → find **WhatsApp** → click **Set Up**. 5. You now have a WhatsApp-enabled Meta App. > **Warning:** > Meta may require **Business Verification** before granting production API access. You can still use test numbers during development without verification. ## Step 2: Get Your WhatsApp API Credentials You need four credentials. Here's where to find each one: ### Phone Number ID Go to your Meta App Dashboard → **WhatsApp** → **Getting Started**. Select your phone number from the dropdown — the **Phone Number ID** is displayed below it. ### WhatsApp Business Account ID On the same **Getting Started** page, find the **WhatsApp Business Account ID** near the top. You can also find it under **Settings** → **WhatsApp Accounts**. ### Permanent Access Token Temporary tokens from the Getting Started page expire in 24 hours. For a permanent token: 1. Go to [business.facebook.com](https://business.facebook.com) → **Settings** → **System Users**. 2. Click **Add** to create a new System User with the **Admin** role. 3. Click **Generate Token** on the System User. 4. Select your WhatsApp Meta App. 5. Grant the following permissions: - `whatsapp_business_messaging` - `whatsapp_business_management` 6. Click **Generate Token** and copy it immediately. > **Warning:** > The token is only shown once. Store it securely — you cannot retrieve it later. ### App Secret Go to your Meta App Dashboard → **Settings** → **Basic** → **App Secret**. Click **Show** to reveal it. This is used for webhook signature verification. ## Step 3: Configure in Reaktly 1. Navigate to your **Operator Dashboard** → **Integrations** → **Add WhatsApp**. 2. Enter the credentials from Step 2: - Access Token - Phone Number ID - WhatsApp Business Account ID - Phone Number (in E.164 format, e.g. `+1234567890`) 3. Reaktly auto-generates a **Webhook URL** and a **Verify Token**. 4. Copy both — you need them for the next step. > **Note:** > If no frontend UI is available yet, you can configure the integration via API. The credential type is `whatsapp_api`: > > ```json > POST /integrations/credentials > { > "type": "whatsapp_api", > "name": "WhatsApp Business", > "data": { > "accessToken": "EAAGn...", > "businessAccountId": "123456789012345", > "phoneNumberId": "987654321098765", > "phoneNumber": "+14155552671", > "apiVersion": "v21.0" > } > } > ``` > > Reaktly then generates the webhook path and verify token; read them back from the connection's webhook info (`GET /integrations/app-connections/{id}/webhook-info`). ## Step 4: Configure Webhook in Meta 1. In your Meta App Dashboard, go to **WhatsApp** → **Configuration**. 2. Under **Webhook**, click **Edit**. 3. Paste the values from Reaktly: - **Callback URL**: the webhook URL shown for the connection in the dashboard. Its shape is `https://api.reaktly.com/webhooks/whatsapp_api/{webhookPath}` — the path segment is randomly generated, not your account ID. - **Verify Token**: the token generated for the connection 4. Click **Verify and Save**. Meta sends a verification request to your endpoint — if it responds correctly, the webhook is confirmed. 5. Under **Webhook Fields**, subscribe to: | Field | Required | Purpose | |---|---|---| | `messages` | ✅ Yes | Incoming messages from customers | | `messaging_postbacks` | Recommended | Button click responses | | `message_deliveries` | Optional | Delivery status tracking | | `message_reads` | Optional | Read receipt tracking | > **Warning:** > The Callback URL **must** use HTTPS and be publicly accessible. Localhost URLs will not work — use a tunnel like ngrok for local development. ## Step 5: Test the Integration 1. In your Meta App Dashboard → **WhatsApp** → **Getting Started**, add a **test phone number**. 2. Send a message from your test number to your WhatsApp Business number. 3. Open the **Reaktly inbox** — a new conversation should appear with a WhatsApp channel badge. 4. Verify that the AI responds and the reply appears back in WhatsApp. If everything is working, you'll see the full conversation thread in both WhatsApp and the Reaktly dashboard. ## Supported Message Types | Type | Inbound | Outbound | Notes | |---|---|---|---| | Text | ✅ | ✅ | Plain text with URL previews | | Interactive Buttons | ✅ | ✅ | Up to 3 buttons, 20 char max per button | | Images | ✅ (as text placeholder) | ❌ Coming soon | | | Documents | ✅ (as text placeholder) | ❌ Coming soon | | | Audio | ✅ (as text placeholder) | ❌ Coming soon | | | Video | ✅ (as text placeholder) | ❌ Coming soon | | | Location | ✅ (as coordinates) | ❌ | | | Stickers | ✅ (as placeholder) | ❌ | | ## AI Behavior on WhatsApp The AI automatically adapts its responses for the WhatsApp medium: - **Concise responses** — messages are kept short and mobile-friendly. - **No markdown tables or headers** — WhatsApp doesn't render them, so the AI avoids these constructs. - **Supported formatting** — bold (`*text*`) and italic (`_text_`) work on WhatsApp and are used when appropriate. - **Link sharing** — the AI suggests visiting your website for detailed information and shares knowledge base articles as text summaries with links. - **Conversational tone** — responses feel natural for a messaging context rather than a formal support channel. ## Phone Number Requirements - Must be in **E.164 format** (e.g., `+1234567890`, `+4915112345678`). - The number must be registered for the **WhatsApp Business API** through Meta. - It **cannot** be currently registered on regular WhatsApp or the WhatsApp Business mobile app. - For production use, the number must have an **approved display name** via Meta. ## Going to Production Once you've tested with development credentials, follow these steps for production: 1. **Complete Meta Business Verification** — go to Business Settings → Business Verification and submit the required documents. 2. **Request production access** for your WhatsApp Business API in the Meta App Dashboard. 3. **Register your production phone number** through the WhatsApp setup flow. 4. **Generate a permanent System User access token** (see [Step 2](#permanent-access-token) above). 5. **Update credentials in Reaktly** with your production values. 6. **Verify your API endpoint** uses a valid SSL certificate from a trusted CA. > **Note:** > Business Verification typically takes 2–5 business days. You can continue developing with test numbers in the meantime. ## Rate Limits & Messaging Tiers WhatsApp enforces messaging limits based on your account tier: - **New accounts** start at **Tier 1** — 1,000 business-initiated conversations per day. - **Customer-initiated conversations** (where the user messages you first) have **no limit**. - Tiers increase automatically as your message volume and quality rating improve. - The quality rating is based on user feedback — if recipients block or report your number, your rating drops. For full details on tier limits and quality ratings, see [Meta's messaging limits documentation](https://developers.facebook.com/docs/whatsapp/messaging-limits). ## Troubleshooting ### Webhook verification fails - Confirm the **Verify Token** matches exactly between Reaktly and Meta (no trailing spaces). - Ensure the **Callback URL** is HTTPS and publicly accessible. - Check that your server responds to `GET` requests with the `hub.challenge` value. ### Messages not arriving in Reaktly - Verify you've subscribed to the **`messages`** webhook field in Meta's Configuration panel. - Check that the webhook status shows a green checkmark (not an error). - Review your server logs for incoming webhook `POST` requests. ### "Access denied" errors when sending replies - Ensure the System User token has the **`whatsapp_business_messaging`** permission. - Verify the token has not been revoked or rotated in Business Settings. ### Phone number format errors - Use E.164 format with the country code — e.g., `+49` for Germany, `+1` for US/Canada. - Do not include spaces, dashes, or parentheses. ### Responses not sent back to WhatsApp - Check that the **access token** is valid and has not expired (use a permanent System User token, not a temporary one). - Confirm the **Phone Number ID** is correct and matches the number receiving messages. - Review the Reaktly logs for outbound API errors. ## Security Notes - **Encryption at rest** — access tokens and credentials are encrypted using AES-256. - **Obfuscated webhook paths** — webhook URLs use randomized paths, not phone numbers or account IDs. - **Webhook signature verification** — for production, enable signature verification using your App Secret. Meta signs every webhook payload with a SHA-256 HMAC, allowing your server to verify that requests genuinely come from Meta. > **Warning:** > Never share your access token or App Secret publicly. Rotate tokens immediately if you suspect they have been compromised. --- # WordPress Integration > Push WordPress posts and pages into a Reaktly knowledge base. URL: https://docs.reaktly.com/docs/integrations/wordpress ## Overview Two ways to get WordPress content into Reaktly: 1. **Push from WordPress** — your site forwards each post to Reaktly when it is saved (recommended: updates arrive immediately) 2. **Pull with a script** — a scheduled job reads the WordPress REST API and pushes Both end in the same place: items in your knowledge base, classified as `ARTICLE` ([Source types](/docs/integrations/source-types)). > **Note:** > A packaged WordPress plugin is on the roadmap. Until it ships, the two approaches below are the supported paths — both are small and self-contained. ## Option 1: Push from WordPress Reaktly exposes a WordPress endpoint that accepts a post object (or an array of them): ``` POST https://api.reaktly.com/integrations/wordpress/sync x-api-key: YOUR_API_KEY ``` Add a small mu-plugin to your theme's `functions.php` (or better, `wp-content/mu-plugins/`): ```php [ 'Content-Type' => 'application/json', 'x-api-key' => 'YOUR_API_KEY', ], 'body' => wp_json_encode([ 'id' => (string) $post->ID, 'title' => ['rendered' => get_the_title($post)], 'content' => ['rendered' => apply_filters('the_content', $post->post_content)], 'link' => get_permalink($post), ]), 'timeout' => 15, ]); }); ``` Every published save re-syncs that post. Send an array to sync several posts in one call. ## Option 2: Pull with a script Use the WordPress REST API when you prefer a scheduled job (for example an initial import): ```python import hashlib import requests WP_URL = "https://your-site.com/wp-json/wp/v2" REAKTLY_API = "https://api.reaktly.com" API_KEY = "your-api-key" def sync_posts(): posts = requests.get(f"{WP_URL}/posts?per_page=100").json() items = [{ "integrationId": "wp-integration-id", "sourceType": "ARTICLE", "externalId": f"wp-post-{post['id']}", "data": { "title": post["title"]["rendered"], "content": post["content"]["rendered"], "url": post["link"], "hash": hashlib.sha256(post["modified"].encode()).hexdigest(), }, } for post in posts] response = requests.post( f"{REAKTLY_API}/ingest/bulk", json={"items": items}, headers={"x-api-key": API_KEY, "Content-Type": "application/json"}, timeout=30, ) response.raise_for_status() print(response.json()["jobCount"], "items queued") ``` The `hash` field makes the job cheap: unchanged posts are skipped ([Change detection](/docs/integrations/change-detection)). > **Warning:** > `content.rendered` arrives as HTML. Reaktly normalises it during processing, but you get cleaner answers when you strip navigation, footers, and shortcodes before sending — send `post_content` rather than a rendered page. ## Pages and custom post types The push endpoint accepts any post-shaped object with an `id`. For pages, use `/wp-json/wp/v2/pages` in the script variant, or the same `save_post` hook filtered to your post types. Give each type a distinct `sourceOrigin` (e.g. `wordpress-posts`, `wordpress-pages`) so [cleanup after a full sync](/docs/api-reference/ingestion#post-ingestcleanup--remove-orphaned-items) stays scoped. --- # Analytics > Track conversation volume, message mix, and response times. URL: https://docs.reaktly.com/docs/platform/analytics The dashboard's overview answers how much is happening and how the load splits between the AI and your team. ## What the overview shows | Metric | Meaning | |---|---| | **Active conversations** | Conversations in progress now — broken down by AI-handled and human-agent-handled | | **Conversations** | Sessions started, for today, this week, and this month | | **Total messages** | Message volume, split into visitor messages and AI messages | | **Average messages per conversation** | How deep exchanges go — a proxy for whether questions get resolved | | **Average response time** | How quickly visitors get an answer | | **Average session duration** | How long conversations run | | **Peak hours** | When your visitors actually show up — useful for staffing a human presence | ## Reading the numbers - **A falling messages-per-conversation with stable volume** usually means answers land on the first try. - **A rising human-agent share** points at a content gap: the AI is handing more over. Check [Conversations](/docs/platform/conversations) for what those visitors asked. - **Peak hours** are the cheapest lever for takeover coverage — staff the two or three busiest hours before adding people anywhere else. ## A weekly routine 1. Compare conversation volume week over week — growth or seasonality, not day-to-day noise. 2. Look at the AI/human split; investigate the conversations that escalated. 3. Check response time against your own service promise. 4. Feed what you learn back into the knowledge base ([Knowledge base management](/docs/platform/knowledge-base)). --- # Conversations > Work your AI conversations in the inbox — triage, takeover, notes, and assignments. URL: https://docs.reaktly.com/docs/platform/conversations The inbox is where conversations with visitors land, whether the AI handled them or a human needs to step in. ## The conversation list Each row is one conversation, filterable by support status: | Status | Meaning | |---|---| | `NEW` | Just arrived, nobody has looked at it | | `OPEN` | Being worked on | | `PENDING` | Waiting on the visitor | | `ON_HOLD` | Paused deliberately | | `RESOLVED` | Answered — no further action expected | | `CLOSED` | Done and closed | A quick filter for **assigned to me** keeps the list to your own work. ## The conversation timeline Everything that happened in a conversation is one timeline: visitor messages, AI answers, replies from your team, internal notes, status changes and handoffs — each attributed to who did it (visitor, AI, agent, or system). The sidebars give context without leaving the conversation: - **Visitor context** — who you are talking to and their earlier conversations - **Notes** — internal notes the visitor never sees - **Triage** — categorise and route the conversation - **Assign** — hand the conversation to a teammate - **Media** — attachments exchanged in the conversation ## When a human should take over The AI answers from your knowledge base. Hand over to a person when: - the visitor asks for a human, - the question needs an account-specific decision the AI cannot make, or - the AI's answer would be a guess — the knowledge base has no match. [Your First Conversation](/docs/getting-started/first-conversation) describes how to close those gaps by improving the source content. ## From conversation to content fix 1. Filter for conversations the AI could not resolve. 2. Read what the visitor actually asked — the wording, not the topic. 3. Add or sharpen the knowledge base item that should have answered it. 4. Confirm the next conversation on that topic resolves without a human. --- # Dashboard Overview > A map of the Reaktly dashboard — operators, inbox, knowledge, workflows, and settings. URL: https://docs.reaktly.com/docs/platform/dashboard-overview ## The main navigation | Section | What lives there | |---|---| | **Dashboard** | Activity overview: active conversations, message volume, peak hours | | **Inbox** | Every conversation with visitors, across operators ([Conversations](/docs/platform/conversations)) | | **Operators** | Your AI assistants — each with its own widget, knowledge, persona, and analytics | | **Contacts** | The people who talked to your assistants | | **Workflows** | Automations and their runs, including approvals | | **Knowledge** | Knowledge bases and their sources ([Knowledge base management](/docs/platform/knowledge-base)) | | **Usage** | Plan consumption and limits | | **Settings** | Profile, security, appearance, team, API keys, integrations, notifications, billing | Reseller and platform-admin accounts see additional sections (partner tenants, admin tooling) that are outside this documentation. ## Inside an operator An operator bundles everything one assistant needs: | Page | What you do there | |---|---| | **Overview** | The assistant's activity at a glance | | **Inbox** | Conversations handled by this operator | | **Knowledge** | Which knowledge bases this operator answers from | | **Persona** | Tone, instructions, and behaviour of the assistant | | **Analytics** | Volume, message mix, response times for this operator | | **Live test** | Try the assistant before visitors do | | **Widget** | Appearance, branding, messages, behaviour, channels, GDPR ([Widget customization](/docs/platform/widget-customization)) and the **Installation** tab with your embed snippet | | **Integrations** | Data sources connected to this operator | | **Forms / Submissions** | Structured input the assistant can collect | | **Routing / Skills / Templates** | Escalation rules, capabilities, and reusable message templates | ## Where to start 1. **Install the widget** — [Widget installation](/docs/getting-started/widget-installation) 2. **Load the knowledge** — [Data ingestion guide](/docs/integrations/ingestion-api) 3. **Shape the assistant** — operator **Persona** and **Widget** 4. **Watch it work** — **Inbox** and [Analytics](/docs/platform/analytics) --- # Platform Guide > Master every feature of the Reaktly platform. URL: https://docs.reaktly.com/docs/platform The Reaktly platform gives you full control over your AI assistant, knowledge base, conversations, and team. - [Dashboard Overview](/docs/platform/dashboard-overview) - [Knowledge Base](/docs/platform/knowledge-base) - [Conversations](/docs/platform/conversations) - [Widget Customization](/docs/platform/widget-customization) - [Team Management](/docs/platform/team-management) - [Analytics](/docs/platform/analytics) --- # Knowledge Base Management > Add, organize, and maintain your AI knowledge base content. URL: https://docs.reaktly.com/docs/platform/knowledge-base Your knowledge base is the core of your Reaktly AI assistant. This guide covers adding content, classifying it, and keeping quality high. ## Adding content 1. **Dashboard entry** — create or edit items directly in the knowledge base view 2. **Ingestion API** — push content programmatically ([Data ingestion guide](/docs/integrations/ingestion-api)) 3. **Built-in connectors** — [Shopify](/docs/integrations/shopify), [WordPress](/docs/integrations/wordpress), [Payload CMS](/docs/integrations/payload-cms) ## Source types Classify every item so the AI can answer with the right context. The full list, with the metadata each type expects, is in [Source types](/docs/integrations/source-types): | Type | Use for | |---|---| | `PRODUCT` | Products with pricing, SKU, inventory | | `ARTICLE` | Blog posts, guides, news | | `SERVICE` | Service offerings with pricing and details | | `PERSON` | Team members, experts, agents | | `BLOG_POST`, `DOCUMENTATION`, `PDF`, `FILE` | Content formats the AI reads as documents | ## Monitoring quality Review actual answers in the [Conversations](/docs/platform/conversations) view to find: - **Missing knowledge** — questions the AI could not answer - **Outdated content** — answers that no longer match reality - **Inaccurate answers** — content that needs to be clearer or more specific Fix problems at the source: update the knowledge base item rather than rewording the question. The AI answers from what you publish. --- # Team Management > Invite team members and manage roles and permissions. URL: https://docs.reaktly.com/docs/platform/team-management ## Inviting team members Navigate to **Settings → Team** to invite colleagues to your workspace. Each member gets their own login — never share credentials. ## Roles | Role | Permissions | |---|---| | **Owner** | Full access, including billing, API keys, and workspace deletion | | **Admin** | Manage team, knowledge base, widget configuration, and integrations | | **Member** | Day-to-day work: conversations, knowledge base content | | **Viewer** | Read-only access to the workspace | ## Access to APIs Dashboard access and API access are separate: - Team roles govern what a person can do after signing in. - [API keys](/docs/api-reference/authentication) govern what a server-side integration can do, and carry their own scopes. Keys are created under **Settings → API keys**, independent of team roles. ## Offboarding 1. Remove the member from the workspace. 2. Review API keys and deactivate any that were used by that person. 3. Rotate shared secrets they had access to, such as integration credentials. --- # Widget Customization > Configure the widget's appearance, copy, behaviour, and channels from the dashboard. URL: https://docs.reaktly.com/docs/platform/widget-customization The widget is configured per operator in the dashboard. Changes are applied the next time the widget loads — no redeploy on your side. ## Appearance | Setting | Effect | |---|---| | **Primary / secondary colour** | Accent colours for the launcher, buttons, and highlights | | **Font family** | Typography inside the chat window | | **Border radius** | Corner rounding of the launcher and window | | **Theme mode** | `auto`, `light`, or `dark`. `auto` follows the visitor's operating system | | **Widget size** | `md` (default) or `lg` | | **Custom CSS** | Additional CSS for cases the presets don't cover | ## Branding Company name, agent name, avatar visibility, and whether the "powered by" badge is shown. ## Messages | Setting | Where it appears | |---|---| | **Welcome message** | First message in a new conversation | | **Placeholder text** | The composer input hint | | **Typing indicator text** | Shown while the AI composes an answer | | **Offline message** | Shown outside your configured availability | ## Behaviour | Setting | Effect | |---|---| | **Auto-open** | Whether the window opens on page load | | **Auto-open delay** | Seconds to wait before auto-opening | | **Close on outside click** | Dismiss the window by clicking the page | | **Show on pages / Hide on pages** | Page rules (e.g. hide on `/checkout`) | ## Home screen and conversation starters The home screen is what visitors see before the first message: logo, greeting, subtext, availability status and expected response time. **Conversation starters** are suggested questions on that screen. You can enable them, cap how many are visible (`maxVisible`), and choose the display mode. ## Contact info Support email, phone, and URL — offered to visitors who want to reach a human. ## Channels The **WhatsApp** channel can be enabled per operator. Set the business number, a prefilled message, the CTA label, and where the CTA sits. See [WhatsApp integration](/docs/integrations/whatsapp) for the Meta-side setup. ## GDPR and identity | Setting | Purpose | |---|---| | **GDPR consent** | Show a consent message with accept button before the chat starts, linking to your privacy policy | | **Identity verification** | Ask visitors to identify themselves before the conversation begins | > **Note:** > The **AI disclosure** notice ("Answers are generated by AI") is part of the product, not a setting — it is shown to comply with the EU AI Act's transparency requirements and cannot be restyled or removed from the widget. ## Where configuration lives in code If you build your own front end instead of embedding the widget, the same configuration is available as JSON — the theme and text values map to the widget's CSS custom properties (`--solis-*`). The widget applies configuration as inline styles, so theme values win over stylesheet rules; use the configuration options rather than overriding styles.