An import brings a knowledge base from Zendesk Guide, Intercom, Document360, Help Scout Docs, Front Knowledge Base or HelpDocs into your help center. HelpCenter.io's importer reads the source, you choose what to bring, and it creates the categories, articles, languages and images. Afterward, its redirect map pairs every old address with the page that replaced it.
Endpoints
Method and path | What it does | Scope |
|---|---|---|
| Scan a help center |
|
| Choose what to bring |
|
| Run an import |
|
| Follow an import |
|
| Cancel an import |
|
| List imports |
|
| Get the redirect map |
|
Every route needs content.write, the reads included, because an import writes into the help center. Use a Read & write API key, or an OAuth access token with content.write whose person is an Owner or Admin of the help center (name the help center as shown in Choose which help center a request acts on). A Read only key gets 403 Forbidden with insufficient_scope, on GET too.
Scans and runs have limits of their own: 10 scans and 10 runs a minute for each credential, besides the 300 requests a minute that every request counts toward. Choosing and cancelling count toward the 120 writes a minute. See Rate limits.
How an import works
Scan. Send the source's address. The importer recognizes the platform, reads the source while you wait, and answers with the import and its
plan: the source's top-level categories, its languages, and how many articles and images it holds.Choose. Pick categories and languages, and whether articles arrive as drafts or as they are at the source. This step is optional: until you choose, a run brings every category and every language that lands here, as drafts.
Run. The answer is
202 Accepted. The import runs in the background.Follow. Read the import until its
statusiscompletedorfailed.Redirect. Publish the articles you keep, then read the redirect map and post it to
POST /v1/redirects/bulk, so the old help center's links lead to the new pages once its address points here.
Import one source at a time: scanning another address cancels a scan you have not run yet. Running an import again updates what it brought before and adds nothing twice.
Sources
Source |
| What to send |
|---|---|---|
Zendesk Guide |
| The help center's address. For a private help center, also |
Intercom |
| The address of a public help center. |
Document360 |
| The address of a public knowledge base. |
Help Scout Docs |
| The address of a public Docs site. |
Front Knowledge Base |
| The address of a public knowledge base. |
HelpDocs |
| The site's address and |
Any other address is other: the importer can't read it by itself. HelpCenter.io's migration team can move such a knowledge base (see How do I get a free migration?), or you can send its articles yourself (see Import articles in bulk).
The import object
Field | Type | Description |
|---|---|---|
| integer | The import's id. |
| string |
|
| boolean |
|
| string | The address you scanned, with |
| string | Its host name. |
| string | Where the import stands. The values are under "Follow an import" below. |
| boolean |
|
| object or null | What the scan found. |
| integer | Articles in the source's main language. |
| array | The source's top-level categories, each with |
| array | The source's languages, each with |
| string | The |
| integer | Images the scan counted. For a large source, an estimate. |
| object | What a run brings: |
| string or null |
|
| string or null | Why a run would write nothing: set when the help center's main language changed after the import wrote into it. |
| object or null | The 500 articles an AI agent's import into an unclaimed help center is held to: |
| object or null | The last run's counts. |
| integer | Articles this import brought that still exist here. A translation shares its article. |
| object |
|
| object or null | After a failed run: |
| string or null | When the last run started and ended, ISO 8601. |
| string | ISO 8601, with an offset. |
Credentials are never returned.
Scan a help center
Send the address readers open, such as the help center's home page, and confirm that you own the source or may copy its content. The scan reads the source during the request, for up to 40 seconds, so give your HTTP client at least a minute (with cURL, --max-time 90).
/v1/importsScan a help center on another platform and answer with the import and its plan. Waits while the source is read, up to 40 seconds.
Field | Type | Description |
|---|---|---|
| string | Required. The source's address, up to 2,048 characters. Send the address readers see: an |
| boolean | Required. |
| string | For a private Zendesk help center: the email address of a Zendesk agent, up to 255 characters. Send it with |
| string | That agent's Zendesk API token, up to 200 characters. |
| string | For HelpDocs: the account's API key, up to 200 characters. |
Credentials are kept encrypted, used only when the source turns out to be the platform they belong to, never returned, and deleted when you cancel the import.
A new import answers
201 Created. An address you scanned before answers200 OKwith the same import, unless that import was cancelled before it brought anything, or handed to the migration team: then the scan makes a new one. One that isscanned, or cancelled after it brought something, is scanned again: its plan is refreshed, and its choice of categories and languages goes back to everything (arrive_asstays). One that is scheduled, queued, running, completed or failed comes back as it is, without a new scan; send new Zendesk or HelpDocs credentials with it to replace the ones it holds.Scanning another address cancels every other import that is still
scanned, not run yet. Choosing or running a cancelled import answers409 Conflictwithimport_cancelled.An address the importer can't read is not an error: the answer is
201withimportable: false,platform: "other"andplan: null. Checkimportablebefore you go on. Running such an import answers409withnot_importable. An address is read as one of the six platforms only when it is on the platform's own domain, or its DNS points there with a CNAME, as a custom domain set up with the platform does; any other address isother, whatever its pages look like.
A source the importer recognizes but can't read, and an address it refuses, answer 422 Unprocessable Entity. The body has status error, a code, the platform (null when the address was refused before the platform was known) and a message written for a person. In the messages below, {host} stands for the source's host name.
| When | Message, for example |
|---|---|---|
| A private Zendesk help center, a HelpDocs site without |
|
| The knowledge base isn't public. |
|
| The source is larger than the importer brings by itself, or too slow to scan in time. |
|
| The source's robots.txt asks crawlers not to read it, or to wait longer between pages than a scan can. |
|
| The source answered in a way the importer can't use, or the help center lives at another address. |
|
| The host name has no DNS record. |
|
| The source stopped answering. |
|
| The source redirects nowhere, or too many times. |
|
| The address isn't on the public internet. |
|
| The address itself can't be fetched. |
|
| Two scans or runs are already reading that site, across every help center importing from it. |
|
| The site has had 20,000 of the importer's requests today, across every help center importing from it. |
|
Choose what to bring
Send the category ids and language codes from the import's plan, as strings, and how the articles should arrive. Choosing starts nothing.
/v1/imports/{importId}Choose the categories and languages to bring, and whether the articles arrive as drafts. Starts nothing.
Field | Type | Description |
|---|---|---|
| array of strings | Required. Ids from |
| array of strings | Required. Codes from |
| string |
|
Send
category_idsandlanguageson everyPUT. An empty list answers422withChoose at least one category to import.orChoose at least one language to import.; a value from outside the plan, withChoose categories from the import's plan (plan.categories[].id).orChoose languages from the import's plan that land here (plan.languages[] with a lands_in).For a Front or Intercom source, a choice with more article pages than one import reads, counting each language, answers
422onlanguages:What you chose comes to {n} articles, counting each language, more than the {limit} we import ourselves. Leave a language or a category out.An import with
importable: falsehas no plan to choose from, so everyPUTanswers422.The
statusdoesn't change. While the import is queued or running, the answer is409withimport_running; for a cancelled import,409withimport_cancelled. You can choose again after a run, then run it again.
Run an import
Start the import with what is chosen. It runs in the background: the answer is 202 Accepted with the import, its status now queued.
/v1/imports/{importId}/runStart the import with what is chosen. The run happens in the background: follow it with GET /v1/imports/{importId}.
Running a
completedorfailedimport runs it again: an article it brought before is updated, never added a second time, andprogressthen describes the new run.A run from the API runs once. A recurring sync is set up in the dashboard only.
An import that is queued or running answers
409withimport_running, so a second click does nothing. A cancelled one answers409withimport_cancelled: scan its address again to set it up anew. One withimportable: falseanswers409withnot_importable.When the help center's main language changed after the import wrote into it, the answer is
409withmain_language_changedand the message inrefusal:The articles from {host} were imported into {language}, and your main language is now {language}, so nothing more will be imported until you switch it back in Settings, under Localization.
Follow an import
Read the import every 10 seconds or so until status is completed or failed. Each read counts toward the 300 requests a minute only.
/v1/imports/{importId}Get an import: its plan, what is chosen, how the last run went, and the articles that wait for review.
| Meaning |
|---|---|
| Scanned, not run yet. |
| Set up in the dashboard to run later. Running it from the API starts it now. |
| Waiting to run. A large import also goes back to |
| Importing. |
| Everything you chose is in. |
| The run stopped. |
| Cancelled with |
| Handed to HelpCenter.io's migration team from the dashboard. It doesn't run from the API. |
In a shell, with jq:
while :; do
STATUS=$(curl -sS "https://api.helpcenter.io/v1/imports/$IMPORT_ID" \
-H "apikey: $HELPCENTER_API_KEY" -H "Accept: application/json" | jq -r .import.status)
echo "$STATUS"
case "$STATUS" in completed|failed|cancelled) break ;; esac
sleep 10
done
Progress
Field | Description |
|---|---|
| The chosen articles, as the scan counted them in the main language. |
| Main-language articles handled so far. It is saved every 10 articles, so it moves in steps, and it never goes back. Translations don't count in it. |
| Articles created, and articles brought before that were updated. |
| Articles that changed both at the source and here since the last run. Your version is kept. |
| Articles brought before that are no longer at the source. They stay here. |
| Images copied into the help center, and images that could not be copied. |
An article is flagged for review, and listed in needs_review with a note, when it changed both at the source and here, when it is no longer at the source, when only some readers could see it at the source, or when the source holds something the importer doesn't bring, such as files attached to a Front article.
A failed run sets error.message and error.reason. reason is a scan code from "Scan a help center" above, such as needs_credentials or too_large, main_language_changed, limit_reached, not_on_platform (the source's address no longer points at its platform), or null with Something went wrong while importing. Our team has been told; you can try again. or The import stopped before it finished. You can try again.
What an import brings
Categories: each chosen top-level category and everything under it, in the source's order and hierarchy, visible to everybody. A category you deleted here since an earlier run is not brought back, and one you renamed keeps its name.
Articles: filed in their categories, as drafts or published as
arrive_assays, with links between imported articles rewritten to their new addresses. An article that only some readers could see at the source arrives private and as a draft. Imported articles have noexternal_id.Languages: the source's main language lands in the help center's main language. Every other chosen language lands in the language its
lands_innames, which is added to the help center when it doesn't have it yet. A translation goes into the same article as its original, and is published only withas_is, when it is published at the source.Images: copied into the help center. An image that can't be copied keeps its original address and counts in
progress.image_failures.Running again updates what the import brought before and adds nothing twice. An article that changed both at the source and here since the last run keeps your version and is flagged; an article no longer at the source is flagged and never deleted.
The importer never deletes anything in your help center, and it doesn't bring a source's comments, users or analytics.
Cancel an import
Cancel an import that is not running. It never runs again, any credentials it held are deleted, and everything it already brought stays. Cancelling a cancelled import answers 200 again.
/v1/imports/{importId}Cancel an import that is not running. Nothing it already brought in is deleted.
An import that is queued or running answers 409 with import_running. Wait for the run to end, then cancel it. To import the same address again later, scan it: what the cancelled import brought is recognized, so nothing arrives twice.
List imports
The help center's imports, newest first, in every status, including imports a person set up in the dashboard. This is how you find a source someone connected there, to follow it and run it here.
/v1/importsList the help center's imports, newest first: up to 50, in every status, imports set up in the dashboard included.
The list holds the 50 newest imports and has no paging. An older import is still readable by its id.
Get the redirect map
The map pairs each old address the import brought, every category, section and article in every language, with the path of its page here. Use it to keep the old help center's links working once its address points to your help center (see Custom domain).
/v1/imports/{importId}/redirect-mapGet each old address the import mapped, with the path of its page here, 200 to a page, in the shape POST /v1/redirects/bulk takes.
Pages of 200. Each page holds as many entries as one
POST /v1/redirects/bulktakes. Read?page=2and on, up tometa.total_pages;meta.items_countcounts the entries on every page. A page past the last has an emptyredirects.In the bulk endpoint's shape. Each entry is
{"from", "to"}:fromis the old address's path,tothe page's current path here.A long old path is given by its id alone where the source's addresses allow it, such as
/hc/en-us/articles/360001, which forwards that address with any title after the id.left_outlists, whole on every page, each old address the map can't give, as{"source_url", "reason"}, wheresource_urlis the full old address.reasonissource_path_too_long(no form of the old path fits in a redirect),target_path_too_long(the page's address here is too long: shorten that article's slug) orsource_answered_here(the help center answers that address itself, so a redirect from it would never be used). Pages deleted here since the import are in neither list.Any import, any time. The map has no status check: it gives what is mapped so far, which is nothing for an import that hasn't run.
Post a page as it is: the bulk endpoint reads its redirects and ignores the other fields. A page with no entries answers 422, so skip it. Any other page answers 207 whatever its entries did, so read meta.failed.
curl -sS "https://api.helpcenter.io/v1/imports/$IMPORT_ID/redirect-map?page=1" \
-H "apikey: $HELPCENTER_API_KEY" -H "Accept: application/json" \
| curl -sS -X POST "https://api.helpcenter.io/v1/redirects/bulk" \
-H "apikey: $HELPCENTER_API_KEY" -H "Accept: application/json" \
-H "Content-Type: application/json" --data-binary @-
Posting the map again re-points what is there and adds nothing, so read it again before you apply it again: an old copy would undo a change made since. A redirect can lead only to a page that exists now: an entry whose article is still a draft, or private, fails with target_not_found in the bulk answer while the rest of the page is saved. Publish those articles, read the map again and post it again. Every rule for redirects is in Redirects.
With an AI agent's key
On a help center an AI agent created and nobody has claimed yet, the agent's key uses these endpoints with a few differences. It sends no confirm_ownership, because the person who claims the help center confirms instead, and no credentials (422 credentials_not_accepted): a private source answers 422 needs_credentials, and its user connects it from the dashboard after the claim. A HelpDocs site is read from its public pages. Its imports carry a limit object, and a choice or a run that doesn't fit in the 500 articles the help center may hold answers 422 limit_reached and saves nothing. A run also stops with limit_reached once the help center's images and files reach 500 MB. While a migration request holds the help center, its writes answer 409 migration_in_progress. After the claim, a key its user let finish setting up sends confirm_ownership: true like any other key, and still sends no credentials. The whole flow is in Migrate a knowledge base as an AI agent.
Errors
Status | Code or message | When |
|---|---|---|
|
| No key or token, or one the API doesn't accept. |
|
| A Read only key, or an OAuth token without |
|
| An OAuth token whose person writes content but isn't an Owner or Admin of the help center, such as an Editor. |
|
| An OAuth token whose person's role doesn't write content, such as a Viewer or Translator. |
|
| An id that doesn't exist or belongs to another help center. |
|
| Choosing, running or cancelling while the import is queued or running. |
|
| Choosing or running a cancelled import, or one handed to the migration team. |
|
| Running an import whose |
|
| Running after the help center's main language changed. |
|
| A field is missing or invalid. |
| A scan code, such as | The scan can't read the source. See "Scan a help center" above. |
|
| An 11th scan or run in a minute, or another rate limit. Wait |
See Errors for the shapes every endpoint shares.