Skip to content

Articles

Search and create knowledge base articles programmatically. Articles can also be managed through the dashboard and are published to your public knowledge base portal.

Which project?

Every article belongs to one project. A key limited to one project implies it — send nothing extra. A key that reaches several projects (the kind you give a coding agent) names the project on each request with the Helmdesk-Project header (slug or id) or ?project=; without it the API answers 400 project.required with the projects it could have meant. Every object in a response carries projectId and projectSlug, so you can always tell what you are looking at.

curl
curl https://helmdesk.dev/api/v1/articles/search?q=export \
  -H "Authorization: Bearer sk_live_..." \
  -H "Helmdesk-Project: ledgerly"
SDK
// Pin the project once — or set HELMDESK_PROJECT and omit it.
const helmdesk = new HelmdeskClient({ apiKey, project: 'ledgerly' })
await helmdesk.articles.search('export')

See Authentication for how keys and projects fit together.

GET/api/v1/articles/search

Search published knowledge base articles by keyword and optionally filter by category.

Request

const results = await helmdesk.articles.search({
  q: 'reset password',
  category: 'getting-started',
})
POST/api/v1/articles

Create a knowledge-base article. The slug is auto-generated from the title when omitted. Defaults to a draft; pass status 'published' to make it live (published articles are indexed for search). Requires the articles:write scope.

Request

const article = await helmdesk.articles.create({
  title: 'How to reset your password',
  body: '1. Open Settings...\n2. Click "Reset password"...',
  status: 'published',
})
GET/api/v1/articles/:id

Get a single article by id, including its full markdown body. Requires the articles:read scope.

Request

const article = await helmdesk.articles.get('article-uuid')
// article.body -> full markdown
PATCH/api/v1/articles/:id

Update an article's content, category, or status. Publishing for the first time stamps publishedAt and indexes the article for search. Changing the slug to one that already exists returns a 409 conflict. Requires the articles:write scope.

Request

await helmdesk.articles.update('article-uuid', {
  body: 'Revised steps...',
  status: 'published',
})
GET/api/v1/article-categories

List the project's article categories — the valid categoryId values for create and update. Requires the articles:read scope.

Request

const { categories } = await helmdesk.articles.categories()

Managing the knowledge base

Search only sees what is published. The management endpoints see everything in the request's environment — drafts included — and let you organise and remove articles through the same code path the dashboard uses.

GET/api/v1/articlesarticles:read

List the project's articles, newest-updated first, drafts included. Filter by status (draft | published), categoryId, or a title/slug substring; page and limit (max 100). Rows carry no body — GET /articles/:id has it.

Request

// SDK
const { articles, total } = await helmdesk.articles.list({ status: 'draft', limit: 50 })

// curl
curl "https://helmdesk.dev/api/v1/articles?status=draft&limit=50" \
  -H "Authorization: Bearer sk_live_..." \
  -H "Helmdesk-Project: abc-salon"

Response

{
  "articles": [
    {
      "id": "9c1f2e3d-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
      "projectId": "7f3a9c2e-4b1d-4e8f-a6c5-2d9b8e7f1a30",
      "projectSlug": "abc-salon",
      "title": "How to reset your password",
      "slug": "how-to-reset-your-password",
      "status": "draft",
      "excerpt": null,
      "categoryId": null,
      "helpfulYes": 0,
      "helpfulNo": 0,
      "publishedAt": null,
      "createdAt": "2026-09-09T10:00:00.000Z",
      "updatedAt": "2026-09-09T10:00:00.000Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 50
}
POST/api/v1/article-categoriesarticles:write

Create a category. The slug is derived from the name when omitted; a slug already used in the project + environment is a 409.

Request

// SDK
const category = await helmdesk.articles.createCategory({
  name: 'Getting started',
  description: 'First steps in the app',
})

// curl
curl -X POST https://helmdesk.dev/api/v1/article-categories \
  -H "Authorization: Bearer sk_live_..." \
  -H "Helmdesk-Project: abc-salon" \
  -H "Content-Type: application/json" \
  -d '{"name":"Getting started","description":"First steps in the app"}'

Response

{
  "id": "5d6e7f80-9a1b-4c2d-8e3f-4a5b6c7d8e9f",
  "name": "Getting started",
  "slug": "getting-started",
  "description": "First steps in the app",
  "sortOrder": 0,
  "projectId": "7f3a9c2e-4b1d-4e8f-a6c5-2d9b8e7f1a30",
  "projectSlug": "abc-salon"
}
DELETE/api/v1/articles/:idarticles:write

Permanently delete an article. It disappears from the public help centre and from search at once; images it referenced from the article-images bucket are removed and their bytes released. To unpublish without losing it, PATCH status to draft instead.

Request

// SDK
await helmdesk.articles.delete('article-uuid')

// curl
curl -X DELETE https://helmdesk.dev/api/v1/articles/article-uuid \
  -H "Authorization: Bearer sk_live_..." \
  -H "Helmdesk-Project: abc-salon"

Response

{
  "deleted": true,
  "id": "9c1f2e3d-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "imagesRemoved": 0,
  "projectId": "7f3a9c2e-4b1d-4e8f-a6c5-2d9b8e7f1a30",
  "projectSlug": "abc-salon"
}

Public knowledge base

Published articles are automatically available at your project's public portal: helmdesk.dev/p/your-slug/articles. No API key is required to view published articles through the portal.