---
title: "LiveAgent to Desk365 Knowledge Base Migration: Technical Guide"
slug: liveagent-to-desk365-knowledge-base-migration-technical-guide
date: 2026-08-14
author: Raaj
categories: [Knowledge Base, Migration Guide, Help Desk]
excerpt: "Technical guide for migrating your knowledge base from LiveAgent to Desk365 — covering API extraction, hierarchy mapping, image re-hosting, and SEO redirects."
tldr: "Extract via LiveAgent API, reshape categories into Desk365's Category → Folder → Article hierarchy, re-host inline images, and cut over with 301 redirects."
canonical: https://clonepartner.com/blog/liveagent-to-desk365-knowledge-base-migration-technical-guide/
---

# LiveAgent to Desk365 Knowledge Base Migration: Technical Guide


# LiveAgent to Desk365 Knowledge Base Migration: Technical Guide

> [!NOTE]
> **TL;DR: LiveAgent → Desk365 Knowledge Base Migration**
>
> LiveAgent has no built-in KB export — you must extract articles via the LiveAgent API (v1 `GET /knowledgebase/entries` or v3 KB endpoints). Desk365's API v3 exposes KB Categories, Folders, and Articles endpoints for programmatic creation. The core structural challenge: LiveAgent uses a **Category → Article** model with nested sub-categories, while Desk365 enforces a three-level **Category → Folder → Article** hierarchy. Non-article LiveAgent content (forums, feedback boards, suggestions) has no equivalent in Desk365. LiveAgent's API is capped at 180 requests/minute; Desk365 rate limits are plan-based — as low as 100 calls/hour on Standard. Budget 1–3 weeks depending on article volume, attachment count, and multi-KB complexity.
>
> **Quick links:**
> - [The Ultimate Knowledge Base Migration Checklist](https://clonepartner.com/blog/blog/the-ultimate-knowledge-base-migration-checklist-a-zero-downtime-plan/)
> - [How to Export Data from Desk365](https://clonepartner.com/blog/blog/how-to-export-data-from-desk365-methods-api-limits-portability/)
> - [LiveAgent to Zoho Desk Migration](https://clonepartner.com/blog/blog/liveagent-to-zoho-desk-migration-the-complete-technical-guide/)
>
> *API behaviors verified against LiveAgent API v1/v3 docs and Desk365 API v3 docs. Validate all endpoint availability against your account tier before running production scripts.*

Migrating a knowledge base from LiveAgent to Desk365 is not a copy-paste job. LiveAgent operates as a standalone multi-channel platform with a customer portal model — articles, forums, feedback boards, suggestion categories, and an optional multi-knowledgebase plugin. Desk365 is a Microsoft 365-native helpdesk with a structured KB built around a **Category → Folder → Article** hierarchy, deep Teams integration, and granular visibility controls.

If you're making this move, you're likely consolidating support into your existing Microsoft infrastructure or replacing LiveAgent's KB with Desk365's tighter integration between tickets and knowledge content. Either way, this is an API-led migration. LiveAgent has no built-in KB export, and the two platforms' data models don't align cleanly enough for a simple dump-and-load.

This guide covers the full extraction, transformation, and loading process — including the structural mismatches, image re-hosting, pagination handling, retry logic, and SEO preservation steps that catch most teams off guard.

## LiveAgent and Desk365 Use Different KB Models

### LiveAgent KB Data Model

LiveAgent's KB is part of its **Customer Portal** feature and includes:

- **Categories** — support nested sub-categories (categories containing other categories)
- **Articles** — HTML content via WYSIWYG editor, with internal (agent-only) and public visibility
- **Forums** — community discussion threads
- **Feedback boards** — customer feature requests and suggestions
- **Multi-Knowledgebase** — optional plugin allowing separate portals with unique domains, designs, and content

> [!WARNING]
> **No native export exists.** LiveAgent confirms there is "no direct export or download option available within the LiveAgent Knowledgebase interface." You must use the API to retrieve articles programmatically. ([support.liveagent.com](https://support.liveagent.com/840770-Complete-API-reference))

LiveAgent exposes KB operations across **two API versions**:

- **API v1** (deprecated): Includes `GET /knowledgebase/entries` for retrieving all articles, returning up to 500 results per call with `page` and `per_page` parameters for pagination. Deprecated — no new endpoints will be added — but existing endpoints remain functional.
- **API v3** (active): Under active development. The following KB-relevant endpoints are confirmed in v3: `GET /v3/kb/categories`, `GET /v3/kb/articles`. However, several KB entry types (forums, suggestions, feedback) are only retrievable via v1 endpoints. Plan to use both versions during extraction.

The rate limit is **180 requests per minute per API key** for cloud accounts. Standalone installations can override this. ([support.liveagent.com](https://support.liveagent.com/217359-Request-rate-limits))

### Desk365 KB Data Model

Desk365's KB follows a strict three-level hierarchy:

| Level | Object | Key Attributes |
|-------|--------|----------------|
| 1 | **Category** | `category_name`, `category_description`, `support_portal_visibility` (1–4), `agent_portal_visibility` (1–2), `category_order` |
| 2 | **Folder** | `folder_name`, `category_name`, `parent_category_name` |
| 3 | **Article** | `article_title`, `article_content` (HTML), `status` (0=Draft, 1=Published), `category_name`, `folder_name`, visibility, attachments |

Visibility controls are more granular than LiveAgent's binary internal/public model:

- **Support portal visibility:** 1 = Not visible, 2 = Visible to all visitors, 3 = Signed-in users only, 4 = Signed-in users from specific companies
- **Agent portal visibility:** 1 = All agents, 2 = Only agents in creator's group(s)

Desk365 allows **unlimited KB articles** on all paid plans. The API v3 exposes dedicated endpoints for KB Categories (`POST /apis/v3/kb/categories`), KB Folders (`POST /apis/v3/kb/folders`), and KB Articles (`POST /apis/v3/kb/articles`).

**Desk365 authentication:** All API v3 requests require an `Authorization` header containing your API key as a plain token — not Bearer, not Basic. The correct format is:

```
Authorization: YOUR_DESK365_API_KEY
```

This is a custom header scheme. Requests using `Bearer YOUR_KEY` or `Basic base64(key)` will return 401. Retrieve your key from Desk365 Settings → API.

> [!CAUTION]
> **Desk365 API rate limits are plan-dependent and tight on lower tiers:**
> - **Standard:** 100 API calls per hour
> - **Plus:** 50 API calls per minute
> - **Premium:** 50 API calls per minute
>
> Loading 500 articles with attachments on Standard takes a minimum of 5 hours for article creation alone — not counting category/folder creation or attachment uploads. Upgrade to Plus or Premium before running a migration script, or throttle aggressively.
>
> **When you exceed the limit, Desk365 returns HTTP 429 with a `Retry-After` header specifying seconds to wait.** Your migration script must handle this explicitly — a silent failure here causes partial loads that are difficult to detect without per-article validation.

**Duplicate article handling:** If you POST an article with a title identical to an existing Desk365 article in the same folder, Desk365 creates a second article rather than returning an error. This means a failed-and-retried migration run can double your article count silently. Maintain a local record of successfully created article IDs and check against it before each POST.

### What Does Not Map 1:1

| LiveAgent Element | Desk365 Equivalent | Migration Decision |
|-------------------|-------------------|-------------------|
| Nested sub-categories (3+ levels) | No equivalent — max depth is Category → Folder | Flatten to two levels; encode deeper path in folder name or article breadcrumb |
| Forums | None | Archive, convert high-value threads to articles, or move to Discourse/Canny |
| Feedback boards | None | Same options as forums |
| Suggestion categories | None | Archive or move to a dedicated feedback tool |
| Per-article `keywords`, `metadescription` | Not exposed in Desk365 API | Manual QA item; embed keywords in article body |
| Mixed visibility within one category | Categories and folders inherit a single visibility level | Must split into separate Desk365 categories by visibility level |
| `article_views`, `article_likes`, `article_dislikes` | Tracked in Desk365 but cannot be seeded via API | Analytics reset to zero; export separately if content strategy depends on engagement data |

## The Folder Problem

This is the most common friction point. LiveAgent stores articles directly under categories (with optional sub-categories). Desk365 **requires** articles to live inside folders, which live inside categories. You cannot skip the folder level — a POST to `/apis/v3/kb/articles` without a valid `folder_name` returns a validation error.

Your options:

1. **Default folder per category.** Create a single folder matching the category name inside each Desk365 category. Fast, but you lose the opportunity to restructure.
2. **Content-based restructuring.** Audit articles before migration and group them into logical folders. More work upfront, but a cleaner Desk365 KB from day one.
3. **Hybrid.** Default folders for small categories (<10 articles), restructured folders for large ones.

For deeper LiveAgent hierarchies, convert root categories to Desk365 categories and second-level sub-categories to folders. For third-level or deeper branches, flatten into the folder name or preserve the breadcrumb in the article body or title:

- `Product A > Billing > Invoices > EU VAT` in LiveAgent becomes **Category:** Billing, **Folder:** Invoices, **Article title:** "EU VAT – Invoice FAQ"

For most teams with 200+ articles, option 2 or 3 pays for itself in reduced maintenance later.

## Field Mapping: LiveAgent → Desk365

| LiveAgent Field | Desk365 Field | Notes |
|----------------|---------------|-------|
| Category | Category | Direct map for top-level. Nested sub-categories become folders. |
| *(none)* | Folder | LiveAgent has no folder concept. Create folders per your strategy above. |
| Article title | `article_title` | Direct map. |
| Article body (HTML) | `article_content` | HTML transfer. Inline image URLs must be rewritten before load. |
| Internal / Public | `support_portal_visibility` + `agent_portal_visibility` | Internal → visibility 1 (not visible to support portal) + agent visibility 1. Public → visibility 2. |
| Multi-KB portal (`kb_id`) | Multi-brand KB | Map each LiveAgent portal to a Desk365 brand. One-to-one is easiest to QA. |
| Forums | *(no equivalent)* | Archive, convert selectively to articles, or migrate to a dedicated tool. |
| Feedback boards | *(no equivalent)* | Same options as forums. |
| Tags / keywords | *(limited)* | Desk365 relies on category/folder structure and search. Embed important keywords in article content. |
| Attachments | Attachments | Must download from LiveAgent and re-upload to Desk365. Inline images need URL rewriting. |
| `article_views`, `article_likes` | *(resets to zero)* | No API to seed historical engagement data. Export separately if relevant to content decisions. |

## Step-by-Step Migration Process

### Step 1: Audit Your LiveAgent KB

Before writing any code, inventory what you have:

- Total article count (public + internal), broken down by entry type
- Number of categories, sub-categories, and multi-KB portals
- Forum topics and feedback board entries (these won't migrate to Desk365)
- Articles with inline images or file attachments (estimate image count and total file size — a KB with 2,000 inline images across 300 articles can require 10–50 GB of local staging storage depending on image resolution)
- Articles with custom CSS or JavaScript embeds
- Deepest category nesting level

Use the LiveAgent API to pull a complete article list. The v1 endpoint returns up to 500 results per page — you must paginate explicitly:

```bash
# First page
curl -X GET "https://yourdomain.ladesk.com/api/v1/knowledgebase/entries?page=1&per_page=500" \
  -H "apikey: YOUR_API_KEY"

# Second page
curl -X GET "https://yourdomain.ladesk.com/api/v1/knowledgebase/entries?page=2&per_page=500" \
  -H "apikey: YOUR_API_KEY"
```

A single GET without pagination parameters returns the first 500 results and silently drops the rest. Any KB with 501+ entries will appear complete when it isn't — this is the most common data loss bug in DIY extractions.

Save results locally as JSON. Store source IDs, parent IDs, entry types (`A` = article, `F` = forum, `S` = suggestion, `T` = topic), status, access flags, and `date_changed` values. The `date_changed` field makes **delta sync** practical near cutover — replay only articles modified after the initial extraction to keep any authoring freeze short.

### Step 2: Extract Articles, Categories, and Attachments

For each article, extract:
- Title and HTML body
- Category assignment and parent hierarchy
- Internal/public visibility flag
- Attached files and inline image URLs
- Locale/language identifier (field: `lang_id` in v1 responses)

Download all attachments and images locally. Inline images hosted on LiveAgent's servers (`*.ladesk.com`) will return 404 after you cancel your LiveAgent subscription.

**Storage estimate before you start:** Count your total attachment and inline image references, then multiply by average file size. A migration with 500 articles averaging 4 inline images each at 200 KB per image requires approximately 400 MB of local staging storage — manageable. A media-heavy KB with 3,000 articles and 10 images each at 1 MB each requires 30 GB. Provision accordingly before extraction begins.

```python
import requests
import json
import time
import os

API_KEY = "your_liveagent_api_key"
BASE_URL = "https://yourdomain.ladesk.com/api/v1"
HEADERS = {"apikey": API_KEY}

def get_all_kb_entries():
    entries = []
    page = 1
    per_page = 500

    while True:
        response = requests.get(
            f"{BASE_URL}/knowledgebase/entries",
            headers=HEADERS,
            params={"page": page, "per_page": per_page}
        )
        response.raise_for_status()
        data = response.json()
        batch = data.get("entries", [])

        if not batch:
            break

        entries.extend(batch)
        print(f"Page {page}: retrieved {len(batch)} entries (total so far: {len(entries)})")

        if len(batch) < per_page:
            break  # Last page

        page += 1
        time.sleep(0.35)  # Stay safely under 180 req/min

    return entries

def download_attachment(url, save_path, api_key):
    resp = requests.get(url, headers={"apikey": api_key}, stream=True)
    resp.raise_for_status()
    os.makedirs(os.path.dirname(save_path), exist_ok=True)
    with open(save_path, "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)

entries = get_all_kb_entries()
with open("liveagent_kb_export.json", "w") as f:
    json.dump(entries, f, indent=2)

print(f"Total entries extracted: {len(entries)}")
```

**Build a staging layer** between extraction and loading. Don't transform directly from a LiveAgent API response into a Desk365 create call. A staging record separates source facts from target decisions and gives you a clean rollback point:

```yaml
source_kb_id: kb_defa
source_entry_id: 288
source_type: A
source_path: Product A > Billing > Invoices
source_lang_id: en
target_brand: Product A
target_category: Billing
target_folder: Invoices
target_status: draft
target_visibility: signed_in_users
redirect_from: /288-vat-invoice-faq
desk365_article_id: null   # Populated after successful POST; prevents duplicate creation on retry
```

The `desk365_article_id` field is critical. Before each article POST, check whether this field is already populated in your staging record. If it is, skip the create call. This prevents the silent duplicate-creation problem described above.

### Step 3: Design the Desk365 Category and Folder Structure

Before loading anything, map out your target structure:

```
Desk365 Category: "Getting Started"  [support_portal_visibility: 2]
  └── Folder: "Account Setup"
      ├── Article: "How to create your account"
      └── Article: "Configuring your profile"
  └── Folder: "Billing"
      ├── Article: "Payment methods"
      └── Article: "Invoice FAQ"

Desk365 Category: "Internal Ops"  [support_portal_visibility: 1]
  └── Folder: "Escalation Procedures"
      └── Article: "Tier 2 Escalation Guide"   [previously "internal" in LiveAgent]
```

If your LiveAgent account uses multiple knowledgebases, decide whether each `kb_id` becomes a separate Desk365 brand or whether you're consolidating. One-to-one mapping is easier to QA and redirect.

**Visibility split planning is mandatory at this stage.** Identify every LiveAgent category that contains a mix of public and internal articles. Each such category must become two separate Desk365 categories — one with `support_portal_visibility: 2` for public content, one with `support_portal_visibility: 1` for internal. Discovering this mid-load causes internal documentation to be briefly exposed on the public portal, which has happened in migrations where this step was skipped.

### Step 4: Create Categories and Folders via Desk365 API

Create categories first, then folders — the Desk365 API requires parent objects to exist before children can reference them. **Set visibility rules before loading any articles** so private content never lands under a public branch.

```bash
# Create a category
curl -X POST "https://yoursubdomain.desk365.io/apis/v3/kb/categories" \
  -H "Authorization: YOUR_DESK365_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "category_name": "Getting Started",
    "category_description": "Onboarding and setup guides",
    "support_portal_visibility": 2,
    "agent_portal_visibility": 1
  }'

# Create a folder within that category
curl -X POST "https://yoursubdomain.desk365.io/apis/v3/kb/folders" \
  -H "Authorization: YOUR_DESK365_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "folder_name": "Account Setup",
    "category_name": "Getting Started"
  }'
```

> [!WARNING]
> **Visibility inheritance is not obvious.** Desk365 folders inherit their parent category's visibility setting. There is no folder-level visibility override. A category set to `support_portal_visibility: 2` (all visitors) will expose every article in every folder under it — including any internal articles you load before setting visibility correctly. Create the category structure with correct visibility codes before loading a single article.

### Step 5: Transform, Re-host Images, and Load Articles

This is where most custom migration scripts fail. Article HTML from LiveAgent contains image tags pointing to LiveAgent's servers:

```html
<img src="https://yourdomain.ladesk.com/scripts/file.php?id=12345">
```

Push this HTML unchanged into Desk365 and images display fine — until you cancel LiveAgent. Then every image returns 404.

**Image processing workflow:**

1. **Parse the HTML.** Use a DOM parser (BeautifulSoup in Python, Cheerio in Node.js) to find all `<img>` tags.
2. **Download the binary.** Make an authenticated GET request to LiveAgent. Pass your API key in the `apikey` header — some images require authentication.
3. **Upload to target.** Upload to Desk365's attachment endpoint or a CDN you control. Azure Blob Storage is a natural fit if you're already in the Microsoft ecosystem.
4. **Rewrite the HTML.** Replace the old `src` URL with the new URL in the article body.
5. **Load the article.** Only after rewriting should you push to Desk365.

For each article, also:
- Strip LiveAgent-specific CSS classes and markup
- Check for `<script>` tags — Desk365's HTML sanitizer strips them; articles relying on inline JavaScript need manual rewrites
- Check for `<iframe>` embeds (YouTube, Vimeo, Wistia) — validate these are preserved after load, as iframe allowlists vary by Desk365 configuration
- Map the internal/public flag to Desk365's numeric visibility codes
- Assign the correct `category_name` and `folder_name` from your staging mapping

**Load articles as Draft (`status: 0`) for review.** The following script includes exponential backoff on 429 responses — hardcoded `time.sleep()` values are insufficient because Desk365's 429 includes a `Retry-After` header that specifies the actual wait time:

```python
import requests
import time
import json

DESK365_API_KEY = "your_desk365_api_key"
DESK365_BASE = "https://yoursubdomain.desk365.io/apis/v3"
HEADERS = {
    "Authorization": DESK365_API_KEY,
    "Content-Type": "application/json"
}

def create_article_with_retry(article_data, max_retries=5):
    """
    POST an article to Desk365 with exponential backoff on 429.
    Returns the created article's ID on success, raises on persistent failure.
    """
    for attempt in range(max_retries):
        response = requests.post(
            f"{DESK365_BASE}/kb/articles",
            headers=HEADERS,
            json=article_data
        )

        if response.status_code == 201:
            return response.json()

        elif response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            print(f"Rate limited. Waiting {retry_after}s (attempt {attempt + 1}/{max_retries})")
            time.sleep(retry_after)

        elif response.status_code == 422:
            # Validation error — log and skip, don't retry
            print(f"Validation error for article '{article_data.get('article_title')}': {response.text}")
            return None

        else:
            wait = (2 ** attempt) * 5
            print(f"HTTP {response.status_code}. Retrying in {wait}s...")
            time.sleep(wait)

    raise Exception(f"Failed to create article after {max_retries} attempts: {article_data.get('article_title')}")


def load_staging_records(path="staging.json"):
    with open(path) as f:
        return json.load(f)

def save_staging_records(records, path="staging.json"):
    with open(path, "w") as f:
        json.dump(records, f, indent=2)


staging = load_staging_records()

for record in staging:
    # Skip already-created articles to prevent duplicates on retry runs
    if record.get("desk365_article_id"):
        print(f"Skipping already-created: {record['target_title']}")
        continue

    article_payload = {
        "article_title": record["target_title"],
        "article_content": record["transformed_html"],
        "category_name": record["target_category"],
        "folder_name": record["target_folder"],
        "status": 0  # Draft
    }

    result = create_article_with_retry(article_payload)

    if result:
        record["desk365_article_id"] = result.get("id") or result.get("article_id")
        save_staging_records(staging)  # Persist after each success
        print(f"Created: {record['target_title']} → ID {record['desk365_article_id']}")

    # Base throttle between successful calls (Standard: 100/hr ≈ 36s apart)
    time.sleep(1.5)
```

> [!WARNING]
> **Standard plan users:** 100 API calls/hour means loading 500 articles takes a minimum of 5 hours for article creation alone. Factor this into your timeline or upgrade before migrating.

### Step 6: Fix Internal Links

Article HTML often contains hyperlinks to other LiveAgent articles:

```html
<a href="https://support.yourcompany.com/Knowledgebase/Article/View/50">Billing FAQ</a>
```

If you don't update these, users clicking them in Desk365 hit dead pages.

**Fix programmatically:**

1. During migration, maintain a URL mapping table: `{old_liveagent_url: new_desk365_url}`. Populate it as each article is created in Desk365.
2. After loading all articles, run a second pass over Desk365 articles via `GET /apis/v3/kb/articles` and then `PATCH /apis/v3/kb/articles/{id}`.
3. Parse each article's HTML for `href` attributes matching your old LiveAgent domain pattern.
4. Look up each old URL in your mapping table, replace with the new Desk365 URL, and update the article via PATCH.
5. Log any old URLs with no mapping entry — these require manual resolution.

### Step 7: Handle Multi-Language Articles

If you used LiveAgent's translation features, each article variant carries a `lang_id` field in the v1 API response (e.g., `en`, `de`, `fr`). Extract each language variant as a separate record during Step 2.

Desk365's localization support depends on your plan tier — verify whether your Desk365 plan exposes multi-language article creation via the API before assuming a direct mapping. If localization isn't available on your tier, you have two options: create language-specific folders (e.g., "Account Setup – DE") or consolidate multi-language content into English-only articles and handle localization post-migration.

### Step 8: Validate and Publish

After loading articles as drafts:

1. **Spot-check rendering.** Open 10–20% of articles in the Desk365 support portal preview. Look for broken formatting, missing images, and mangled tables.
2. **Verify visibility.** Confirm internal articles aren't publicly visible. Test both agent portal URLs and support portal URLs — Desk365 exposes separate share URLs for each. A visibility misconfiguration here exposed internal documentation to the public portal in at least one migration we observed where the visibility-split step was deferred to post-load.
3. **Check folder structure.** Verify no articles landed in the wrong folder. Cross-reference your staging records against the Desk365 article list via `GET /apis/v3/kb/articles`.
4. **Test search.** Run key queries against the Desk365 support portal search. Verify articles surface for expected terms.
5. **Confirm no duplicates.** Check article counts per folder against expected counts from your staging records.
6. **Publish in batches.** Flip articles from Draft to Published via PATCH or through the Desk365 agent portal. Publish internal categories first to avoid any gap where agents need the content but it isn't live.

### Step 9: SEO Redirects and Sitemap

If your LiveAgent KB is public, it's indexed by search engines and bookmarked by customers. Changing platforms changes your URL structure entirely.

LiveAgent article URLs use auto-generated numerical IDs that cannot be customized. Desk365 URLs follow a different routing pattern with no documented mechanism to preserve source URLs. ([support.liveagent.com](https://support.liveagent.com/896745-Managing-Knowledge-Base-Content))

**Redirect implementation:**

Neither platform handles external 301 redirects out of the box. Handle this at your DNS/CDN layer (Cloudflare, AWS CloudFront, Azure Front Door):

1. Export your final URL mapping (old LiveAgent URL → new Desk365 URL) from your staging records.
2. Format the list as redirect rules (Cloudflare Bulk Redirect List, Nginx `rewrite` directives, etc.).
3. Apply rules at your edge so old URLs issue 301 Permanent Redirects before the request reaches either helpdesk.
4. Submit an updated sitemap to Google Search Console and monitor crawl coverage of new URLs.

Google's [site move guidance](https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes) is clear: build a one-to-one old→new URL map, enable permanent redirects, update internal links, submit updated sitemaps, and monitor the new URLs in Search Console.

## Edge Cases and Failure Modes

**Mixed-visibility categories (highest risk).** A source LiveAgent category containing both public and internal articles that isn't split before loading will expose internal documentation on the support portal. This is not a hypothetical — in migrations where the visibility split is deferred to "after we confirm the structure looks right," the window between article creation and visibility correction has resulted in internal escalation procedures and agent notes being visible to end users. Split before you load anything.

**Duplicate articles on retry.** Desk365's article POST creates a new record rather than returning a conflict error when the same title exists in the same folder. A failed migration run retried without checking existing state will double your article count. The staging layer with `desk365_article_id` tracking in Step 2 prevents this.

**Multi-Knowledgebase migration.** Each LiveAgent portal (`kb_id`) maps to a separate Desk365 multi-brand KB. Run extraction and loading once per portal, targeting the correct brand. Cross-portal article references need manual resolution.

**Video embeds.** Articles containing YouTube, Vimeo, or Wistia iframes may have iframes stripped by Desk365's HTML sanitizer. Test with one embedded article in your Desk365 sandbox before assuming iframes are preserved at scale.

**Custom CSS and scripts.** LiveAgent allows extensive custom CSS and inline JavaScript. Desk365 strips `<script>` tags. Complex table layouts referencing portal-specific CSS classes will lose their styling. Identify these articles during audit and flag for manual reconstruction.

**Multi-language articles.** The `lang_id` field in LiveAgent v1 API responses carries the locale identifier. Extract each language variant as a separate record. Verify Desk365 localization API support on your tier before planning a multi-language automated load.

**HTML compatibility.** LiveAgent's and Desk365's WYSIWYG editors produce different markup. Custom CSS classes referencing portal-specific styles won't exist in Desk365. Test code blocks, tables, and nested list formatting after the first batch load.

**Attachment upload validation.** Desk365's public API exposes a `has_attachments` field on article records, but the dedicated bulk KB attachment upload endpoint is not fully documented as of this writing. Validate attachment upload behavior in a Desk365 sandbox environment on your actual plan tier before committing to a fully automated attachment migration.

**Forums and feedback boards.** Don't force forum content into KB articles wholesale — the format doesn't translate well and will clutter your Desk365 KB. Archive, selectively convert high-value threads, or migrate community content to a dedicated tool (Discourse, Canny, Productboard).

## API Error Reference

| HTTP Status | Platform | Meaning | Action |
|------------|----------|---------|--------|
| 429 | Desk365 | Rate limit exceeded | Read `Retry-After` header; wait specified seconds before retry |
| 422 | Desk365 | Validation error (missing required field, invalid visibility code, etc.) | Log the response body; fix the payload; do not retry automatically |
| 401 | Desk365 | Invalid or missing API key | Verify `Authorization` header format (not Bearer, not Basic) |
| 404 | Desk365 | Category or folder referenced in article POST does not exist | Create parent objects first; check for typos in `category_name`/`folder_name` |
| 429 | LiveAgent | 180 req/min exceeded | Back off for 60 seconds; add `time.sleep(0.35)` between all extraction calls |
| 500 | Either | Server error | Log response body; retry with exponential backoff (max 5 attempts) |

## What You Lose in This Migration

Be explicit with stakeholders before committing:

- **Forums and feedback boards** — no Desk365 equivalent; requires a separate tool or archival decision
- **Historical view/like/dislike counts** — reset to zero; no API endpoint to seed historical engagement data
- **Custom portal CSS/themes** — must be recreated in Desk365's portal designer from scratch
- **Per-article SEO metadata** — LiveAgent's `keywords` and `metadescription` fields don't map to Desk365; must be re-entered manually or embedded in article body
- **Suggestion categories** — no direct Desk365 equivalent
- **LiveAgent-specific widget embeds** — must be replaced with Desk365's web widget
- **Article-level URL control** — LiveAgent numerical IDs cannot be mapped to Desk365 URLs; only 301 redirects at the CDN/DNS layer bridge the gap

## DIY vs. Managed Migration

**DIY is viable when:**
- Fewer than 200 articles
- No multi-KB portals
- Minimal inline images or attachments
- An engineer can dedicate 20–40 hours over 1–2 weeks
- You're on Desk365 Plus or Premium (50 calls/min allows reasonable throughput)

**A managed migration makes more sense when:**
- 500+ articles across multiple portals
- Heavy attachment and inline image usage requiring significant local staging storage
- Content restructuring needed during migration
- Standard plan with 100 API calls/hour constraint makes timeline impractical
- Zero-downtime requirement with delta sync capability
- Your team lacks engineering bandwidth for a multi-week extraction, transform, and load project

### Timeline Estimates

| Scenario | Article Count | Estimated Time |
|----------|--------------|----------------|
| Small, simple KB | <100 articles, no attachments | 3–5 days |
| Mid-size KB | 100–500 articles, some attachments | 1–2 weeks |
| Large multi-portal KB | 500+ articles, multi-KB, heavy media | 2–4 weeks |

These assume a single engineer working part-time. A managed service running parallel workstreams can compress the large scenario to 1–2 weeks.

## After the Migration

Once articles are published, complete these steps to restore full functionality:

1. **Configure the support portal.** Set your custom domain, branding, and access controls in Desk365 Settings.
2. **Enable KB search in Teams.** Desk365 lets agents search and insert KB articles directly from Microsoft Teams — activate this under Integrations if your team works in Teams.
3. **Confirm 301 redirects are active.** If your LiveAgent KB was on a custom domain, verify redirect rules are live and returning HTTP 301 (not 302) for all mapped URLs.
4. **Update internal references.** Any links to your KB from product docs, onboarding emails, or internal wikis need the new Desk365 URLs.
5. **Monitor article feedback.** Desk365 includes built-in article rating (useful/not useful). Track these signals in the first 30 days to catch content gaps created by the structural flattening.
6. **Run a crawl.** Use a tool like Screaming Frog or Sitebulb to crawl your new Desk365 KB and verify no internal links still point to your old LiveAgent domain.

> Migrating your knowledge base from LiveAgent to Desk365? Our engineers handle the extraction, hierarchy mapping, HTML cleanup, image re-hosting, and validation — so you don't have to. Get a free 30-minute migration planning call.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can I export my LiveAgent knowledge base to a file?

No. LiveAgent has no built-in KB export or CSV download. You must use the LiveAgent API (v1 GET /knowledgebase/entries or v3 KB endpoints) to programmatically extract articles, categories, and content. The API rate limit is 180 requests per minute per API key.

### Does Desk365 have an API for importing knowledge base articles?

Yes. Desk365 API v3 exposes endpoints for KB Categories, KB Folders, and KB Articles. You can create all three programmatically. Rate limits depend on your plan: 100 calls/hour on Standard, 50 calls/minute on Plus and Premium.

### How do I migrate inline images from LiveAgent to Desk365?

Use a DOM parser to locate img tags in the article HTML, download the image files from LiveAgent's servers, upload them to Desk365 or a CDN you control (like Azure Blob Storage), and rewrite the HTML src URLs before importing. Skipping this step means every image breaks when you cancel LiveAgent.

### What happens to LiveAgent forums and feedback boards during migration?

Desk365 has no forum or feedback board feature. These cannot be migrated directly. You can archive them, convert high-value threads into KB articles, or migrate community content to a dedicated tool like Discourse or Canny.

### How long does a LiveAgent to Desk365 KB migration take?

A small KB (under 100 articles, no attachments) takes 3–5 days. Mid-size migrations (100–500 articles) take 1–2 weeks. Large multi-portal KBs with 500+ articles and heavy media take 2–4 weeks. These estimates assume a single engineer working part-time.
