# How to manage Help articles via API?

The Help.Center API lets you programmatically manage your help center articles.
Create, update, publish, organize articles, and manage categories without ever
opening the dashboard.

> Tip: Want to skip the manual API calls? Install the Help.Center Agent Skill
> [https://github.com/microdotcompany/helpcenter-skill] to manage articles using
> plain language with AI coding agents like Claude Code, Cursor, or OpenClaw.


BEFORE YOU START

You'll need to create an API key from your Help.Center dashboard:

 1. Go to Settings > General > API

 2. Click "Create API Key"

 3. Enter a name for your key (e.g., "Production", "Development")

 4. Select the scopes you need based on what operations you'll perform

 5. Copy your API Key and save it securely - you won't see it again!

 6. Note your Center ID from the API page

All API requests go to https://api.help.center and require your API key in the
header:

Authorization: Bearer YOUR_API_KEY
Content-Type: application/json


API KEY SCOPES

API keys use a scope-based permission system. When creating a key, select only
the scopes you need:

Scope

Allows

Required Dashboard Permission

content.read

View articles, drafts, categories, and counts

Content Read

content.write

Create and edit articles, drafts, metadata, categories, upload images

Content Edit + Update

content.publish

Publish, unpublish, and duplicate articles

Content Publish

content.delete

Delete articles and categories

Content Delete

> Security Tip: Create multiple API keys with minimal scopes for different
> purposes. For example, use a read-only key for analytics and a separate key
> with write permissions for content management.


QUICK START: CREATE YOUR FIRST ARTICLE

Here's how to create and publish an article in two steps:


STEP 1: CREATE A DRAFT

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting Started with Our Product",
    "content": {
      "html": "<h1>Welcome</h1><p>Your article content here...</p>"
    },
    "category_id": "getting-started"
  }' \
  "https://api.help.center/v0/centers/YOUR_CENTER_ID/articles"


STEP 2: PUBLISH IT

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.help.center/v0/centers/YOUR_CENTER_ID/articles/ARTICLE_ID/publish"

That's it! Your article is now live. Note that all articles are created as
drafts first and must be explicitly published.


API ENDPOINTS


CENTER & CATEGORIES

Action

Method

Endpoint

Required Scope

Get center info

GET

/v0/centers/:centerId

content.read

List categories

GET

/v0/centers/:centerId/articles/categories

content.read

Create category

POST

/v0/centers/:centerId/articles/categories

content.write

Update category

PATCH

/v0/centers/:centerId/articles/categories/:categoryId

content.write

Delete category

DELETE

/v0/centers/:centerId/articles/categories/:categoryId

content.delete


ARTICLES

Action

Method

Endpoint

Required Scope

List articles

GET

/v0/centers/:centerId/articles

content.read

Search articles

GET

/v0/centers/:centerId/articles?search=query

content.read

Get article

GET

/v0/centers/:centerId/articles/:articleId

content.read

Count articles

GET

/v0/centers/:centerId/articles/count

content.read

Create article

POST

/v0/centers/:centerId/articles

content.write

Update metadata

PATCH

/v0/centers/:centerId/articles/:articleId/metadata

content.write

Delete article

DELETE

/v0/centers/:centerId/articles/:articleId

content.delete

Publish

POST

/v0/centers/:centerId/articles/:articleId/publish

content.publish

Unpublish

POST

/v0/centers/:centerId/articles/:articleId/unpublish

content.publish

Duplicate

POST

/v0/centers/:centerId/articles/:articleId/duplicate

content.write


DRAFTS

Action

Method

Endpoint

Required Scope

List drafts

GET

/v0/centers/:centerId/articles/drafts

content.read

Count drafts

GET

/v0/centers/:centerId/articles/drafts/count

content.read

Get draft

GET

/v0/centers/:centerId/articles/:articleId/draft

content.read

Update draft

PATCH

/v0/centers/:centerId/articles/:articleId/draft

content.write

Discard draft

POST

/v0/centers/:centerId/articles/:articleId/draft/discard

content.write


TRANSLATIONS

Action

Method

Endpoint

Required Scope

Get translation

GET

/v0/centers/:centerId/articles/:articleId/translations/:language

content.read

Get draft translation

GET

/v0/centers/:centerId/articles/:articleId/translations/:language/draft

content.read

Update translation draft

PATCH

/v0/centers/:centerId/articles/:articleId/translations/:language/draft

content.write

Update translation metadata

PATCH

/v0/centers/:centerId/articles/:articleId/translations/:language/metadata

content.write

Delete translation

DELETE

/v0/centers/:centerId/articles/:articleId/translations/:language

content.delete

Publish translation

POST

/v0/centers/:centerId/articles/:articleId/translations/:language/publish

content.publish

Unpublish translation

POST

/v0/centers/:centerId/articles/:articleId/translations/:language/unpublish

content.publish


IMAGES

Action

Method

Endpoint

Required Scope

Upload image

POST

/v0/centers/:centerId/articles/images

content.write

Upload for article

POST

/v0/centers/:centerId/articles/:articleId/images

content.write


CATEGORY MANAGEMENT

Categories help organize your articles. You can create hierarchical categories
with one level of subcategories.


CREATING CATEGORIES

POST /v0/centers/:centerId/articles/categories

Request body:

{
  "name": "Getting Started",
  "description": "Articles for new users",
  "icon": "<svg>...</svg>",  // Optional custom SVG icon
  "parent_id": "parent-cat-id"  // Optional, for subcategories
}

Categories automatically get a URL-friendly slug and unique ID. If no icon is
provided, a default book icon is used.


UPDATING CATEGORIES

PATCH /v0/centers/:centerId/articles/categories/:categoryId

{
  "name": "Updated Name",
  "description": "Updated description",
  "icon": "<svg>...</svg>"
}


DELETING CATEGORIES

Categories can only be deleted if no articles are using them:

DELETE /v0/centers/:centerId/articles/categories/:categoryId

If articles exist in the category, you'll get an error with the list of article
IDs that need to be moved or deleted first.


IMAGE UPLOAD

Upload images for use in your articles:

POST /v0/centers/:centerId/articles/images

Request: Use multipart/form-data with field name image

 * Maximum size: 10MB

 * Supported formats: JPEG, PNG, GIF, WebP, SVG

Response:

{
  "success": true,
  "data": {
    "url": "https://cdn.help.center/images/...",
    "filename": "image.png",
    "size": 1024576
  }
}


LISTING ARTICLES

Fetch your published articles with optional filters:

GET /v0/centers/:centerId/articles

Query parameters:

 * limit - Number of results (default: 50, max: 100)

 * starting_after - Cursor for forward pagination (pass the last article's ID)

 * ending_before - Cursor for backward pagination

 * category - Filter by category ID

 * search - Search by title or content

 * expand[]=content - Include full HTML and text content in the response

Response includes new fields:

{
  "id": "article-id",
  "url": "https://support.help.center/article/1091-getting-started",
  "preview_url": "https://support.help.center/article/preview-abc123",
  // ... other fields
}


CREATING ARTICLES

POST /v0/centers/:centerId/articles

Request body:

{
  "title": "Your Article Title",
  "content": {
    "html": "<h1>Title</h1><p>Your content here</p>"
  },
  "category_id": "getting-started",
  "metadata": {
    "seo": {
      "title": "SEO-friendly title (50-60 chars)",
      "description": "Meta description (150-160 chars)"
    }
  }
}

Important: Articles are always created as drafts. You must call the publish
endpoint separately to make them live. HTML content is automatically sanitized
for security.


UPDATING ARTICLES

To update an article's content, use the draft endpoint:

PATCH /v0/centers/:centerId/articles/:articleId/draft

{
  "title": "Updated Title",
  "html": "<h1>Updated Content</h1><p>New content here...</p>"
}

To update metadata (category, slug, SEO) without touching content:

PATCH /v0/centers/:centerId/articles/:articleId/metadata

{
  "category": "new-category-id",
  "slug": "new-article-slug",
  "seo": {
    "title": "New SEO Title",
    "description": "New SEO Description"
  }
}


PUBLISHING & UNPUBLISHING

Publish a draft to make it live:

POST /v0/centers/:centerId/articles/:articleId/publish

Unpublish to revert to draft:

POST /v0/centers/:centerId/articles/:articleId/unpublish


DRAFT MANAGEMENT

Every article has a draft version. When you edit a published article, changes
stay in draft until you publish again.

Draft statuses:

 * unpublished - Never been published

 * published - In sync with the live version

 * published_with_changes - Has unpublished edits

List all drafts:

GET /v0/centers/:centerId/articles/drafts

Supports the same limit, starting_after, category, search, and expand[]
parameters, plus a status filter.

To discard unpublished changes and restore the published version:

POST /v0/centers/:centerId/articles/:articleId/draft/discard


ARTICLE TRANSLATIONS

Manage multilingual content by creating translations of your articles. Each
translation maintains its own draft and published state.


CHECKING AVAILABLE LANGUAGES

Get the center info to see which languages are configured:

GET /v0/centers/:centerId

Response includes:

{
  "id": "center-id",
  "name": "Support Center",
  "domain": "support.help.center",
  "default_language": "en",
  "additional_languages": ["es", "fr", "de", "ja"],
  "created_at": "2024-01-01T00:00:00.000Z"
}

Use the language codes from additional_languages in translation endpoints.
Standard ISO 639-1 codes like es (Spanish), fr (French), de (German), ja
(Japanese), etc.


ARTICLE RESPONSE WITH TRANSLATIONS

When fetching articles, the response includes available translations:

GET /v0/centers/:centerId/articles/:articleId

{
  "id": "article-id",
  "title": "Getting Started",
  "translations": ["es", "fr"],  // Languages with published translations
  // ... other fields
}


CREATING/UPDATING TRANSLATIONS

To create or update a translation, use the draft endpoint with the language
code:

PATCH /v0/centers/:centerId/articles/:articleId/translations/es/draft

{
  "title": "Título del artículo",
  "html": "<h1>Contenido en español</h1><p>...</p>"
}

Important: The language must be in the center's additional_languages array, or
you'll get an error.


PUBLISHING TRANSLATIONS

Translations are managed independently from the main article:

POST /v0/centers/:centerId/articles/:articleId/translations/es/publish

Note: The main article must be published before you can publish any
translations.


FETCHING TRANSLATIONS

Get a specific translation of an article:

GET /v0/centers/:centerId/articles/:articleId/translations/es?expand[]=content

Response:

{
  "id": "article-id",
  "language": "es",
  "title": "Título del artículo",
  "status": "published",
  "url": "https://support.help.center/es/article/1091-articulo",
  "content": {
    "html": "...",
    "text": "..."
  }
}


TRANSLATION WORKFLOW

 1. Check available languages - Use GET center info to see additional_languages

 2. Create/Update Draft - Use PATCH on the translation draft endpoint

 3. Update Metadata - Optionally update SEO and slug for the translation

 4. Publish - Make the translation live (main article must be published first)

 5. Manage Independently - Each translation can be published/unpublished
    separately


TRANSLATION METADATA

Update translation-specific metadata separately from content:

PATCH /v0/centers/:centerId/articles/:articleId/translations/es/metadata

{
  "slug": "empezando",
  "seo": {
    "metaTitle": "Título SEO",
    "metaDesc": "Descripción SEO"
  }
}


IMPORTANT NOTES

 * Translations are tied to the main article - deleting the main article removes
   all translations

 * Each translation has its own draft and published state

 * Translation URLs follow the pattern:
   https://[domain]/[language]/article/[slugId]-[slug]

 * HTML content is automatically sanitized for all translations

 * When unpublishing the main article, all translations are also unpublished

 * Embeddings are created separately for each language to enable
   language-specific search


MANAGING API KEYS

Best practices for API key management:

 * Create multiple keys - Use different keys for different environments
   (production, staging, development)

 * Use minimal scopes - Only grant the permissions each key needs

 * Name keys clearly - Use descriptive names like "Production Read-Only" or
   "CI/CD Publisher"

 * Rotate regularly - Periodically create new keys and revoke old ones

 * Monitor usage - Check your dashboard to see which keys are being used

You can create, update, and revoke keys from the dashboard at any time without
affecting other keys.


PAGINATION

The API uses cursor-based pagination. Use starting_after with the last item's ID
to get the next page:

GET /v0/centers/:centerId/articles?starting_after=last_article_id&limit=10

Check the has_more field in the response to know if more pages exist.


RATE LIMITING

 * 100 requests per minute per API key (burst up to 200)

 * Check response headers: X-RateLimit-Limit, X-RateLimit-Remaining,
   X-RateLimit-Reset

 * If you hit the limit, wait until the reset time before retrying


ERROR HANDLING

The API returns standard HTTP status codes with descriptive error messages:

 * 400 - Bad request (missing fields, invalid parameters)

 * 401 - Invalid or missing API key

 * 403 - Insufficient permissions (missing required scope)

 * 404 - Article, category, or center not found

 * 429 - Rate limited

 * 500 - Server error

Error responses include a type, message, and code to help you debug:

{
  "error": {
    "type": "invalid_request_error",
    "message": "You do not have the 'content.publish' scope",
    "code": "insufficient_scope"
  }
}


CODE EXAMPLES


JAVASCRIPT / NODE.JS

const axios = require('axios');

const api = axios.create({
  baseURL: 'https://api.help.center',
  headers: {
    'Authorization': `Bearer ${process.env.HC_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

// List articles
const { data } = await api.get(
  `/v0/centers/${centerId}/articles`,
  { params: { category: 'getting-started', limit: 10 } }
);

// Create and publish an article
const article = await api.post(`/v0/centers/${centerId}/articles`, {
  title: 'New Article',
  content: { html: '<p>Content</p>' },
  category_id: 'guides'
});

await api.post(`/v0/centers/${centerId}/articles/${article.data.id}/publish`);


PYTHON

import requests

headers = {
    'Authorization': f'Bearer {api_key}',
    'Content-Type': 'application/json'
}

# List articles
response = requests.get(
    f'https://api.help.center/v0/centers/{center_id}/articles',
    headers=headers,
    params={'category': 'getting-started', 'limit': 10}
)
articles = response.json()

# Upload an image
with open('image.png', 'rb') as f:
    files = {'image': f}
    response = requests.post(
        f'https://api.help.center/v0/centers/{center_id}/articles/images',
        headers={'Authorization': f'Bearer {api_key}'},
        files=files
    )
    image_url = response.json()['data']['url']


BEST PRACTICES

 1. Always use HTTPS - All API requests must use HTTPS

 2. Keep your API keys secret - Never expose them in client-side code or public
    repositories

 3. Use appropriate scopes - Only request the permissions you need

 4. Use pagination - Don't try to fetch all articles at once

 5. Use expand[] wisely - Only request content when you need it to reduce
    bandwidth

 6. Handle errors gracefully - Check status codes and parse error responses

 7. Respect rate limits - Implement exponential backoff when you hit limits

 8. Sanitize content - Although the API sanitizes HTML, validate user input on
    your end too