API Documentation
Use the REST API to programmatically manage your helpdesk content
Connecting Claude, ChatGPT, Cursor or another AI agent? Use the MCP server instead — it exposes the knowledge base as tools with OAuth or a personal token.
Create a category and article in 3 steps
const API_KEY = "hdh_your_api_key";
const BASE_URL = "https://helpdesky.io/api/v1";
const headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
};
// 1. Create a category
const catRes = await fetch(`${BASE_URL}/categories`, {
method: "POST",
headers,
body: JSON.stringify({
name: "Getting Started",
description: "Help users get set up",
}),
});
const { data: category } = await catRes.json();
// 2. Create an article in that category
const articleRes = await fetch(`${BASE_URL}/articles`, {
method: "POST",
headers,
body: JSON.stringify({
title: "How to create your first project",
content: "## Welcome\n\nFollow these steps to get started:\n\n1. Click **New Project**\n2. Enter a name\n3. Choose a template\n\nThat's it!",
categoryId: category.id,
published: true,
}),
});
const { data: article } = await articleRes.json();
console.log("Created:", article.title);
All API requests require an API key sent via the X-API-Key header.
Generate your API key from your dashboard under Settings > API Integration.
curl -X GET https://helpdesky.io/api/v1/categories \ -H "X-API-Key: hdh_your_api_key_here"
Base URL: https://helpdesky.io/api/v1
Content Type: application/json
CRUD operations for organising articles into categories
/api/v1/categories
List all categories in your helpdesk
Example
curl -X GET https://helpdesky.io/api/v1/categories \ -H "X-API-Key: hdh_your_api_key"
Response
{
"data": [
{
"id": "abc123",
"name": "Getting Started",
"slug": "getting-started",
"description": "Introductory guides",
"icon": "folder",
"order": 0
}
]
}
/api/v1/categories
Create a new category
Request Body
{
"name": "Getting Started", // required
"description": "Intro guides", // optional
"icon": "book-open" // optional, default: "folder"
}
Example
curl -X POST https://helpdesky.io/api/v1/categories \
-H "X-API-Key: hdh_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Getting Started", "description": "Introductory guides"}'
Response
{
"data": {
"id": "abc123",
"name": "Getting Started",
"slug": "getting-started",
"description": "Introductory guides",
"icon": "folder",
"order": 0
}
}
/api/v1/categories/:id
Update a category. Only include the fields you want to change.
Request Body
{
"name": "New Name", // optional
"description": "Updated desc", // optional
"icon": "star", // optional
"order": 1 // optional
}
Example
curl -X PATCH https://helpdesky.io/api/v1/categories/CATEGORY_ID \
-H "X-API-Key: hdh_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Name"}'
Response
{
"data": {
"id": "abc123",
"name": "Updated Name",
"slug": "getting-started",
"description": "Introductory guides",
"icon": "folder",
"order": 0
}
}
/api/v1/categories/:id
Delete a category and all its articles
Example
curl -X DELETE https://helpdesky.io/api/v1/categories/CATEGORY_ID \ -H "X-API-Key: hdh_your_api_key"
Response
{
"data": {
"success": true
}
}
CRUD operations for managing helpdesk articles
/api/v1/articles
List all articles. Optionally filter by category using the categoryId query parameter.
Example
curl -X GET "https://helpdesky.io/api/v1/articles?categoryId=CATEGORY_ID" \ -H "X-API-Key: hdh_your_api_key"
Response
{
"data": [
{
"id": "def456",
"title": "How to reset your password",
"slug": "how-to-reset-your-password",
"content": "## Steps\n\n1. Click **Forgot Password**...",
"excerpt": "Learn how to reset your password",
"published": true,
"categoryId": "abc123",
"order": 0
}
]
}
/api/v1/articles
Create a new article. Content should be in Markdown format.
Request Body
{
"title": "How to reset your password", // required
"content": "## Steps\n\n1. Click...", // required, Markdown
"slug": "reset-password", // optional, auto-generated from title if omitted
"excerpt": "Short summary", // optional
"categoryId": "abc123", // optional
"published": true, // optional, default: false
"numberHeadings": true, // optional, default: false — numbers H2 headings
"hiddenFromWidget": false // optional, default: false — hide article from the embedded widget while keeping it on the public help center
}
Example
curl -X POST https://helpdesky.io/api/v1/articles \
-H "X-API-Key: hdh_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "How to reset your password",
"content": "## Steps\n\n1. Click **Forgot Password**\n2. Enter your email\n3. Check your inbox",
"categoryId": "CATEGORY_ID",
"published": true
}'
Response
{
"data": {
"id": "def456",
"title": "How to reset your password",
"slug": "reset-password",
"content": "## Steps\n\n1. Click **Forgot Password**...",
"excerpt": null,
"published": true,
"numberHeadings": false,
"hiddenFromWidget": false,
"categoryId": "abc123",
"order": 0
}
}
/api/v1/articles/:id
Update an article. Only include the fields you want to change. Changing the slug on a published article automatically creates a 301 redirect from the old URL.
Request Body
{
"title": "Updated Title", // optional
"content": "New content...", // optional, Markdown
"slug": "new-url-handle", // optional — auto-redirect created for published articles
"excerpt": "Updated summary", // optional
"categoryId": "abc123", // optional
"published": true, // optional
"numberHeadings": true, // optional — numbers H2 headings
"hiddenFromWidget": true, // optional — hide from the embedded widget while keeping it on the public help center
"order": 2 // optional
}
Example
curl -X PATCH https://helpdesky.io/api/v1/articles/ARTICLE_ID \
-H "X-API-Key: hdh_your_api_key" \
-H "Content-Type: application/json" \
-d '{"published": true}'
Response
{
"data": {
"id": "def456",
"title": "How to reset your password",
"slug": "new-url-handle",
"content": "## Steps...",
"published": true,
"numberHeadings": true,
"categoryId": "abc123",
"order": 0
}
}
/api/v1/articles/:id
Delete an article permanently
Example
curl -X DELETE https://helpdesky.io/api/v1/articles/ARTICLE_ID \ -H "X-API-Key: hdh_your_api_key"
Response
{
"data": {
"success": true
}
}
Upload images to use in article content
/api/v1/images
Upload an image file. Returns a URL you can embed in article content using HTML or Markdown (see format options below). Maximum file size is 5MB. Allowed types: JPG, PNG, GIF, WebP, SVG.
Request
Send a multipart/form-data request with the image in a field named image.
Example
curl -X POST https://helpdesky.io/api/v1/images \ -H "X-API-Key: hdh_your_api_key" \ -F "image=@/path/to/screenshot.png"
Response
{
"url": "/api/images/uploads/USER_ID/abc123-screenshot.png",
"filename": "screenshot.png",
"size": 245760,
"contentType": "image/png"
}
Use the returned url in your article content. There are two formats you can use:
Option 1: HTML (recommended)
Use an HTML <img> tag to control the display size with width and/or height attributes. This is the same format used by the dashboard visual editor.
<img src="/api/images/uploads/USER_ID/abc123-screenshot.png" width="400" />
Option 2: Markdown
Standard Markdown image syntax. Simple, but the image always displays at its full original size — there is no way to set dimensions.

Tip: We recommend HTML for most use cases — it gives you full control over how large images appear in your articles. Both formats are supported anywhere in your article content and can be mixed freely.
All errors return a JSON object with an error field:
401 Missing or invalid API key
{
"error": "Missing X-API-Key header"
}
400 Missing required fields or invalid data
{
"error": "Title is required"
}
404 Resource not found or doesn't belong to your helpdesk
{
"error": "Category not found"
}
429 Rate limit exceeded (60 requests per minute per API key)
{
"error": "Rate limit exceeded. Please try again later."
}
500 Server error
{
"error": "Internal server error"
}
Supported syntax for article content
Article content uses Markdown. In addition to standard syntax (headings, bold, italic, lists, links, images, code blocks), we support:
Rendering articles on your own site? The Content API returns this syntax already rendered as HTML, together with a stylesheet for it — you never need to render the Markdown yourself.
Tables
Use pipe syntax to create tables. The first row becomes the header.
| Feature | Status | Notes | | ----------- | --------- | ----------------- | | Markdown | Supported | Full GFM syntax | | Tables | Supported | Auto-styled | | Callouts | Supported | info/warning/danger |
Callout Blocks
Highlight important information with callout blocks using blockquote syntax:
> [!info] > This is an informational note. > [!warning] > Be careful with this setting. > [!danger] > This action cannot be undone.
YouTube Embeds
Paste a YouTube URL on its own line to auto-embed the video:
https://www.youtube.com/watch?v=VIDEO_ID
Buttons
Turn any link into a call-to-action button by adding {.btn} (filled) or {.btn-outline} (outline) after the link. By default the link opens in a new tab with rel="noopener". Add .same-tab to open in the same tab, and .nofollow to add rel="nofollow".
[Start free trial](https://helpdesky.io/register){.btn}
[Read the docs](https://helpdesky.io/api-docs){.btn-outline .same-tab}
[Sponsored partner](https://example.com){.btn .nofollow}
Action Cards
Action cards are tinted, icon-led tiles that link somewhere. Add {.card} after a link and optionally set icon and desc. Place two or more cards on adjacent lines and they automatically lay out as a responsive 2-column grid (single column on mobile). Add sameTab to open in the same tab and nofollow to add rel="nofollow"; both default to off.
[Install the widget](/help/getting-started){.card icon="rocket" desc="Add Helpdesky to your site in two minutes."}
[Invite your team](/dashboard/staff){.card icon="user-plus" desc="Give teammates access to the inbox."}
Supported icon values:
user-plus
play
book-open
rocket
settings
mail
download
link
info
check
sparkles
code
desc is optional. Unknown icon names fall back to info.
Public, read-only endpoints for headless help centers: fetch published articles as finished HTML and render them inside your own site
Use the Content API when help articles should appear inside your own website templates (your header, footer and CSS) instead of on the hosted help center. Your site fetches an article and injects the returned HTML; a standalone stylesheet reproduces callouts, buttons, action cards, code blocks, numbered headings, embeds, images and tables exactly as the hosted page shows them. The same model Ghost and Contentful use.
Building it with an AI coding agent? Point it at the self-contained spec helpdesky.io/docs/headless.md: endpoints, the HTML vocabulary, the URL structure to serve, SEO and redirect rules, and acceptance criteria.
Authentication
None. The Content API is unauthenticated and only ever returns published content. Address a help center by its slug — the last segment of its Helpdesky address (https://helpdesky.io/help/{helpdesk-slug}, shown under Settings → Domain & Address).
Caching, CORS and rate limits
Every response carries Access-Control-Allow-Origin: * (preflight OPTIONS answers 204), Cache-Control: public, max-age=60 and an ETag — send If-None-Match to get 304. Requests are limited to 120 per minute per visitor IP (RateLimit-* headers, 429 when exceeded); when you fetch from your server, forward the visitor's address in X-Forwarded-For so the limit and the view analytics apply per visitor rather than to your server. Cache responses on your side for at least 60 seconds.
The helpdesk object
Every JSON response starts with the same helpdesk object: name and slug, url (the help center's live canonical base URL: your headless address once saved, otherwise its subfolder address, custom domain or Helpdesky address), and the help center's SEO title and SEO description from the dashboard's Settings page, metaTitle and metaDescription (null when unset). Use the SEO fields for your help index page: <title> = metaTitle (fall back to "name Help Center") plus your site name, <meta name="description"> = metaDescription, and the WebSite/CollectionPage JSON-LD — the same values the hosted home page uses. Title category pages "category name - site name" with the category description. The spec's SEO section also covers the BreadcrumbList structured data, the help-center section of your /llms.txt (index plus every published article as - [title](url): excerpt, grouped by category, built from the categories endpoint) and why the sitemap and llms file belong at the root of your origin (/sitemap.xml, /llms.txt).
/api/content/v1/helpdesk/{helpdesk-slug}/articles/{article-slug}
One published article as rendered HTML, with heading IDs, table of contents, category, author display info and its live canonical URL. Returns 404 for drafts and unknown slugs, 410 for deleted articles. Each fetch counts as an article view (bot user agents excluded).
Example
curl "https://helpdesky.io/api/content/v1/helpdesk/acme/articles/getting-started"
Response
{
"helpdesk": {
"name": "Acme Help", "slug": "acme",
"url": "https://help.acme.com", // live canonical base URL of the help center
"metaTitle": "Acme Help Center: guides and FAQs", // SEO title from Settings (null when unset) — title your index page with it
"metaDescription": "Answers about billing, workspaces and integrations." // SEO description (null when unset) — your index page's meta description
},
"title": "Getting started",
"slug": "getting-started", // current slug — differs from the request when the article was renamed (301 to it)
"excerpt": "Set up your workspace in five minutes.",
"url": "https://help.acme.com/getting-started", // live canonical URL of the hosted article
"html": "<p>Welcome…</p>\n<h2 id=\"create-a-workspace\">Create a workspace</h2>…", // all URLs absolute: uploads on helpdesky.io, internal links under helpdesk.url
"toc": [ { "id": "create-a-workspace", "text": "Create a workspace", "level": 2 } ],
"numberHeadings": false, // true when the hosted page numbers the H2 headings
"category": { "name": "Basics", "slug": "basics", "url": "https://help.acme.com/category/basics" },
"showAuthors": true, // respects the help center's show-author setting and per-article override
"organizationAsAuthor": false, // true when the organisation is credited instead of people
"authors": [ { "name": "Jane Doe", "avatarUrl": "https://…/jane.png", "url": "https://help.acme.com/author/jane-doe" } ],
"createdAt": "2026-03-02T10:15:00.000Z",
"updatedAt": "2026-09-12T08:40:12.000Z"
}
/api/content/v1/helpdesk/{helpdesk-slug}/categories
Categories in display order, each with its published articles (lean: title, slug, excerpt, timestamps, canonical URL — no bodies), plus the uncategorized articles. Each category carries its canonical URL.
Response
{
"helpdesk": { "name": "Acme Help", "slug": "acme", "url": "https://help.acme.com", "metaTitle": "Acme Help Center: guides and FAQs", "metaDescription": "Answers about billing, workspaces and integrations." },
"categories": [
{
"name": "Basics",
"slug": "basics",
"description": "First steps",
"icon": "rocket",
"url": "https://help.acme.com/category/basics",
"articles": [
{
"title": "Getting started",
"slug": "getting-started",
"excerpt": "Set up your workspace in five minutes.",
"url": "https://help.acme.com/getting-started",
"createdAt": "2026-03-02T10:15:00.000Z",
"updatedAt": "2026-09-12T08:40:12.000Z"
}
]
}
],
"uncategorized": []
}
/api/content/v1/helpdesk/{helpdesk-slug}/articles
Flat list of every published article in the same lean shape, each with its category (null when uncategorised). Use it to build routes, sitemaps and the help-center section of your llms.txt.
Response
{
"helpdesk": { "name": "Acme Help", "slug": "acme", "url": "https://help.acme.com", "metaTitle": "Acme Help Center: guides and FAQs", "metaDescription": "Answers about billing, workspaces and integrations." },
"articles": [
{
"title": "Getting started",
"slug": "getting-started",
"excerpt": "Set up your workspace in five minutes.",
"url": "https://help.acme.com/getting-started",
"createdAt": "2026-03-02T10:15:00.000Z",
"updatedAt": "2026-09-12T08:40:12.000Z",
"category": { "name": "Basics", "slug": "basics", "url": "https://help.acme.com/category/basics" }
}
]
}
/api/content/v1/helpdesk/{helpdesk-slug}/search?q={text}
Published articles whose title or body matches q (case-insensitive), in the same lean shape as the flat list plus the echoed query. q is required (400 when missing).
Example
curl "https://helpdesky.io/api/content/v1/helpdesk/acme/search?q=workspace"
/api/content/v1/article.css
Standalone stylesheet for the article HTML (text/css, CORS, cached for a day with stale-while-revalidate and an ETag). Every rule is scoped under one wrapper class, .helpdesky-article, so it cannot leak into your page. Add numbered-headings to the wrapper when numberHeadings is true. Theme it with CSS custom properties set on :root or the wrapper: --helpdesky-accent (default #3b82f6), --helpdesky-text (#1f2937), --helpdesky-font (Inter stack) and --helpdesky-radius (14px). The headless spec lists the optional ones (accent gradient, code background).
Fetch and render
<link rel="stylesheet" href="https://helpdesky.io/api/content/v1/article.css">
<style>:root { --helpdesky-accent: #e11d48; --helpdesky-font: "Source Sans 3", system-ui, sans-serif; }</style>
<h1 id="title"></h1>
<article id="body" class="helpdesky-article"></article>
<script>
const res = await fetch("https://helpdesky.io/api/content/v1/helpdesk/acme/articles/getting-started");
if (res.status === 404) showNotFound();
const article = await res.json();
document.title = article.title;
document.getElementById("title").textContent = article.title;
const body = document.getElementById("body");
body.classList.toggle("numbered-headings", article.numberHeadings);
body.innerHTML = article.html; // finished HTML — do not run it through Markdown or a class-stripping sanitiser
</script>
The html is the output of the Markdown syntax documented above (callouts become <div class="callout callout-info">, buttons <a class="btn-block btn-block-primary"> (consecutive ones inside a <div class="btn-row">), action cards <div class="action-card-grid"> of a.action-card, YouTube links <div class="video-embed"><iframe>, headings carry ids matching toc). It is sanitised for your origin with an allowlist (no scripts, styles, event handlers, javascript: URLs, forms, or iframes other than YouTube embeds), so inject it as is. Every URL in it is absolute, so the HTML works on your origin: uploaded images point at https://helpdesky.io/api/images/… (allow that host in your img-src policy, never rewrite them to your host) and links to other articles are resolved beneath the help center's live address, so they start with helpdesk.url + "/" — replace that prefix with your own base path to keep visitors on your site. For server-side rendering, the URL structure a headless site should serve, SEO and redirect rules, read the headless spec.
Slugs
Slugs are auto-generated from the article title or category name if not provided, but you can set a custom slug when creating an article or category via POST. You can also change the slug later with PATCH. If you change the slug on a published article, a 301 redirect is automatically created from the old URL to the new one.
Public URLs
Article slugs determine the public URL of each article. The URL format depends on whether you use a custom domain:
Path-based (default): https://helpdesky.io/help/{helpdesk-slug}/{article-slug}
Custom domain: https://{your-domain}/{article-slug}
On custom domains, articles are served directly at the root. The /articles path (without a slug) is a separate page that lists all published articles.
Pagination
The API does not currently paginate results. All categories and articles are returned in a single response.
Filtering
Use the categoryId query parameter on GET /api/v1/articles to filter articles by category.
Rate Limiting
The API is limited to 60 requests per minute per API key. If you exceed this limit, you'll receive a 429 status code. Rate limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) are included in every response.
Category Icons
The following icon values are available for categories:
folder
file-text
book-open
lightbulb
help-circle
settings
zap
users
The default icon is folder if none is specified.