# Articles _Category: REST API reference_ Articles are your help center's content. With the Articles API you list, search and read them, create them from HTML or Markdown, create or update up to 50 at a time, change them, move them to Trash, send readers' votes back from wherever else you show them, and read every vote with the message its reader typed. ## Endpoints | Method and path | What it does | Scope | | --- | --- | --- | | `GET /v1/articles` | Lists or searches articles | `content.read` | | `GET /v1/articles/{articleId}` | Gets one article | `content.read` | | `POST /v1/articles` | Creates an article, or updates the one with the same `external_id` | `content.write` | | `POST /v1/articles/bulk` | Creates or updates up to 50 articles | `content.write` | | `PATCH /v1/articles/{articleId}` | Changes some fields of an article | `content.write` | | `DELETE /v1/articles/{articleId}` | Moves an article to Trash | `content.write` | | `POST /v1/articles/{articleId}/feedback` | Records a reader's vote on an article | `content.write` | | `GET /v1/feedback` | Lists readers' votes and the messages they typed, newest first | `analytics.read` | A Read only API key holds `content.read` and `analytics.read`; a Read & write key also holds `content.write`. OAuth tokens need the scope in the table (see [Scopes](https://developers.helpcenter.io/content/scopes)). Staged edits, versions and restores have their own page, [Staged changes and versions](https://developers.helpcenter.io/content/staged-changes-api). The API answers every error in JSON, with or without `Accept: application/json`. Send the header anyway, as the examples do. ## The article object Every endpoint returns articles in this shape. Text fields are maps keyed by language code, and a response always carries every language the article has. ``` { "id": 757, "external_id": "kb/download-invoice", "title": { "de": "Rechnung herunterladen", "en": "Download an invoice" }, "slug": { "de": "rechnung-herunterladen", "en": "download-an-invoice" }, "author": { "id": 414, "name": "Maya Chen", "avatar": null }, "content": { "de": "
Öffnen Sie Abrechnung, dann Rechnungen, und klicken Sie auf PDF herunterladen.
", "en": "Open Billing, then Invoices, and click Download PDF.
" }, "category_id": 333, "categories": [], "type": "article", "published": true, "has_staged_changes": false, "staged_locales": [], "staged_updated_at": null, "visibility": "public", "share_token": null, "views": 0, "audio_file": null, "metadata": { "title": { "en": "Download an invoice as a PDF" }, "description": { "en": "Find every invoice under Billing and download it as a PDF for your records." } }, "ratings": { "thumbs_up": "60", "thumbs_down": "20", "love": "20" }, "published_translations": { "de": "2026-09-30 08:27:03" }, "shared_with": [], "created_at": "2026-09-30 08:26:42", "updated_at": "2026-09-30 08:27:03", "_links": { "view": { "method": "GET", "url": "https://acme.helpcenter.io/content/download-an-invoice" } } } ``` | Field | Type | Description | | --- | --- | --- | | `id` | integer | The article's id. | | `external_id` | string or null | Your own key for the article, when you created or updated it with one. See Create or update by your own key, below. | | `title` | object | The title per language, for example `{"en": "Download an invoice"}`. A language whose title was emptied shows `null`. | | `slug` | object | The URL slug per language. | | `author` | object | `id`, `name` and `avatar` (an image URL or `null`) of the author. An article created over the API is authored by the person who created the API key, or who connected the OAuth app. `name` and `avatar` are `null` when that account no longer exists, for example a team member who left; `id` stays. | | `content` | object | The body per language, as HTML. Internal notes, the highlights of team notes and AI drafts waiting for review are removed from what the API returns. | | `category_id` | integer | The article's category. `0` means Uncategorized. `-1` means the article is in several categories, listed in `categories`. | | `categories` | array of integers | The categories of an article that is in several. Empty for an article in one category, whose category is in `category_id`. | | `type` | string | `article` or `faq`. FAQ articles feed the FAQ sections of your help center. | | `published` | boolean | `true` when the article is live, `false` for a draft. A live article reaches readers once the help center is published too: `GET /v1/sites` reports that as `published`. | | `has_staged_changes`, `staged_locales`, `staged_updated_at` | boolean, array, string or null | Whether edits are staged but not published yet, in which languages, and when they last changed. The rest of the object is always the live article. See [Staged changes and versions](https://developers.helpcenter.io/content/staged-changes-api). | | `visibility` | string | `public`, `private` (only the author and the teammates it is shared with) or `link_only` (anyone with the secret link). See [Choose who can read an article](https://self.helpcenter.io/content/article-access). | | `share_token` | string or null | The secret of a link-only article. The link is the article's address followed by `?share=` and this token. | | `views` | integer | An all-time counter that no longer counts new views. For current traffic, use `views_in_range` from the [Analytics API](https://developers.helpcenter.io/content/analytics-api). | | `audio_file` | object or null | The audio version per language, when the article has one. | | `metadata` | object | The title and description search engines show: `{"title": {...}, "description": {...}}`, each a map of languages. A field with no value is `{}`. | | `ratings` | object | The share of reader votes, in whole percent: `thumbs_up`, `thumbs_down` and `love`, across all languages and all time. Strings such as `"60"` when the article has votes, the number `0` when it has none. | | `published_translations` | object or array | The additional languages released to readers, as keys: `{"de": ...}`. The value is `true` or the time of the release, depending on how it was released, so test for the key. `[]` when none is released. The default language never appears here. | | `shared_with` | array of integers | The ids of the teammates a private article is shared with. | | `created_at`, `updated_at` | string | In UTC, as `YYYY-MM-DD HH:MM:SS`. | | `_links.view` | object | `{"method": "GET", "url": ...}`, the article's public address. On a help center with several languages it redirects to the reader's language. It opens for readers once the article and the help center are published; before that it answers `404` to everyone but the help center's team. | ### Languages and translations - Every key in `title`, `slug`, `content` and `metadata` must be a language your help center has: its default language or an additional one (`GET /v1/sites` lists them). Any other key is refused. - Writes are per language. A language you leave out of a map keeps its value. - Reads always return every language. The `lang` parameter of `GET /v1/articles` only affects `search`. - **Translations you write over the API are not shown to readers yet.** A title and text in an additional language are saved as a translation waiting for release: readers get a not-found page for it until someone releases it, one article at a time with **Released** in the editor (see [Translate an article](https://self.helpcenter.io/content/translate-an-article)) or for the whole language with **Release translated drafts** (see [Release translations and change your default language](https://self.helpcenter.io/content/manage-languages)). On Catalyst, a staged edit can release it too (see [Staged changes and versions](https://developers.helpcenter.io/content/staged-changes-api)). `published_translations` tells you what is released. ## List articles GET`/v1/articles` List the articles of the help center, or search them. Without `search`, the list is your team's view of the help center: every article that is not in Trash, drafts, private and link-only articles included, most recently changed first. | Parameter | Type | Description | | --- | --- | --- | | `limit` | integer | Default 100. From 1 to 100. | | `page` | integer | Default 1. A page past the end returns an empty list. | | `category_id` | integer | Only articles filed in this category, including articles in several categories that list it. Articles in its subcategories are not included. `0` applies no filter. An id that is not one of your categories is a `400`. | | `status` | string | `published` or `draft`. | | `updated_since` | string | Only articles changed at or after this time. Send UTC, such as `2026-09-30 08:00:00` or `2026-09-30T08:00:00Z`, URL-encoded. An offset such as `+02:00` is not converted, so the filter would be off by that many hours. | | `order_by` | string | `views` sorts by all-time views. Without it, the order is last change first. | | `order_type` | string | `desc` (default) or `asc`. | | `search` | string | Keywords. Switches the list to what readers can find: see Search below. | | `lang` | string | The language `search` matches in: your help center's default language (the default) or one of its additional languages (`GET /v1/sites` lists them). Any other value is a `422`. Without `search` it changes nothing and is not checked, because the response always holds every language. | The response's `meta` holds `page`, `per_page`, `total_pages` and `items_count`. To fetch everything with full content, [Export](https://developers.helpcenter.io/content/export-api) walks the whole help center with a cursor. To stay in sync, pass the `updated_at` of your last run as `updated_since`, and use [webhooks](https://developers.helpcenter.io/content/webhooks) to learn about deletions, which a list cannot show. Putting a category's articles in a new order with `PUT /v1/categories/{categoryId}/articles/order` doesn't change their `updated_at`, so `updated_since` doesn't list the articles that moved. ### Search With `search`, the endpoint answers the way your help center's search answers a visitor who is not signed in: - Only published, public articles that readers can open are returned. Drafts, private and link-only articles, and articles in categories readers can't see, are left out, so `status=draft` with `search` returns nothing. A help center that isn't published yet still returns its published articles here. - A search serves **up to 1,000 matches**, best ranked first. A match ranked lower is never returned, on any page. `items_count` counts the matches you can page through, and `limit` (up to 100) and `page` page through them: see [Pagination](https://developers.helpcenter.io/content/pagination). - The best match comes first. `order_by=views` sorts the matches by views instead. - A search matches in your default language, or in the language `lang` names. In an additional language (`lang=de`), only released translations match. - A `lang` your help center does not have, such as `lang=xx`, is refused with `422` and `"status": "validation_error"`. `errors.lang` reads `That language is not enabled on this help center.` It is keyword search, the same for every plan. To find drafts or private articles by their text, list without `search` and filter on your side. ## Get an article GET`/v1/articles/{articleId}` Get one article, in every language it has. Returns any article of the help center that is not in Trash, drafts and private articles included. You always get the live article: staged edits are not merged in. An id from another help center, an unknown id and an article in Trash all answer `404 Not Found` with `Article not found.` This read counts against the limit of 120 writes a minute, not only the 300 requests a minute every call counts against. See [Rate limits](https://developers.helpcenter.io/content/rate-limits). ## Create an article POST`/v1/articles` Create an article, or update the one that already has the same external_id. | Field | Type | Description | | --- | --- | --- | | `title` | object | Required. The title per language: `{"en": "Request a refund"}`. Every title you send needs text: an empty or blank title is refused with `400` naming the language, such as `title.en`, and nothing is saved. | | `content` | object | The body per language, as HTML unless you set `content_format`. Leave it out for an empty article. Markup that would run script on your help center's pages is removed on save, such as `