REST API reference

Imports

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

POST /v1/imports

Scan a help center

content.write

PUT /v1/imports/{importId}

Choose what to bring

content.write

POST /v1/imports/{importId}/run

Run an import

content.write

GET /v1/imports/{importId}

Follow an import

content.write

DELETE /v1/imports/{importId}

Cancel an import

content.write

GET /v1/imports

List imports

content.write

GET /v1/imports/{importId}/redirect-map

Get the redirect map

content.write

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

  1. 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.

  2. 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.

  3. Run. The answer is 202 Accepted. The import runs in the background.

  4. Follow. Read the import until its status is completed or failed.

  5. 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

platform

What to send

Zendesk Guide

zendesk

The help center's address. For a private help center, also zendesk_email and zendesk_token.

Intercom

intercom

The address of a public help center.

Document360

document360

The address of a public knowledge base.

Help Scout Docs

helpscout

The address of a public Docs site.

Front Knowledge Base

front

The address of a public knowledge base.

HelpDocs

helpdocs

The site's address and helpdocs_api_key, the account's API key.

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

id

integer

The import's id.

platform

string

zendesk, intercom, document360, helpscout, front, helpdocs or other.

importable

boolean

false when the importer can't read the source (platform other).

source_url

string

The address you scanned, with https:// added when you left out the scheme and any #fragment dropped.

source_host

string

Its host name.

status

string

Where the import stands. The values are under "Follow an import" below.

agent_initiated

boolean

true when an AI agent's key set it up or ran it last.

plan

object or null

What the scan found. null when importable is false.

plan.articles

integer

Articles in the source's main language.

plan.categories

array

The source's top-level categories, each with id (a string, such as c100), name and articles, which counts everything nested under it. Their sections and subcategories arrive as subcategories.

plan.languages

array

The source's languages, each with code (the source's own, such as en-us), label, articles and lands_in: the help center language it is written into. lands_in is null for a language that can't be imported, because HelpCenter.io doesn't support it, another of the source's languages already lands there, or an earlier run wrote it into a language since removed from the help center.

plan.default_language

string

The code of the source's main language.

plan.images

integer

Images the scan counted. For a large source, an estimate.

selection

object

What a run brings: category_ids, languages, arrive_as and articles, the main-language articles in the chosen categories. Until you choose, every category and every language that lands here.

selection.arrive_as

string or null

draft or as_is. null in the answer to a first scan, where it means draft.

refusal

string or null

Why a run would write nothing: set when the help center's main language changed after the import wrote into it.

limit

object or null

The 500 articles an AI agent's import into an unclaimed help center is held to: max, room, articles_to_import and fits. null for every other import.

progress

object or null

The last run's counts. null until the first run starts.

imported_articles

integer

Articles this import brought that still exist here. A translation shares its article.

needs_review

object

count, and articles: the first 50 imported articles flagged for review, each with id, title and note.

error

object or null

After a failed run: message, and reason, a code or null.

started_at, finished_at

string or null

When the last run started and ended, ISO 8601.

created_at, updated_at

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).

POST/v1/imports

Scan 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

url

string

Required. The source's address, up to 2,048 characters. Send the address readers see: an http:// address, or an alias that redirects to the help center, can come back as other.

confirm_ownership

boolean

Required. true: you own this help center or may copy its content.

zendesk_email

string

For a private Zendesk help center: the email address of a Zendesk agent, up to 255 characters. Send it with zendesk_token.

zendesk_token

string

That agent's Zendesk API token, up to 200 characters.

helpdocs_api_key

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 answers 200 OK with 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 is scanned, 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_as stays). 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 answers 409 Conflict with import_cancelled.

  • An address the importer can't read is not an error: the answer is 201 with importable: false, platform: "other" and plan: null. Check importable before you go on. Running such an import answers 409 with not_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 is other, 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.

code

When

Message, for example

needs_credentials

A private Zendesk help center, a HelpDocs site without helpdocs_api_key, or credentials the source refused.

This help center is private. Add a Zendesk API token to import it. or Add your HelpDocs API key to import this help center.

not_public

The knowledge base isn't public.

This knowledge base is not public, so we cannot read it. Our migration team can move it for you.

too_large

The source is larger than the importer brings by itself, or too slow to scan in time.

{host} took longer to scan than we can wait here: it is very large, or slow to answer. Our migration team can move it for you instead, free.

robots

The source's robots.txt asks crawlers not to read it, or to wait longer between pages than a scan can.

This knowledge base's robots.txt asks crawlers not to read {path}. Our migration team can move it for you.

remote_error

The source answered in a way the importer can't use, or the help center lives at another address.

This Intercom help center lives at {url}. Paste that address to import it.

unresolvable

The host name has no DNS record.

We could not find {host}. Check the address and try again.

unreachable, transport

The source stopped answering.

We could not reach {host}. Check the address and try again. or Something went wrong while reading {host}.

bad_redirect, too_many_redirects

The source redirects nowhere, or too many times.

{host} redirected too many times.

ip_in_blocked_range, resolves_to_blocked_range

The address isn't on the public internet.

{host} is not a public address.

scheme_not_allowed, port_not_allowed, credentials_in_url, unparseable_url

The address itself can't be fetched.

Only addresses on the standard web ports are fetched. or That does not look like a web address.

host_busy

Two scans or runs are already reading that site, across every help center importing from it.

Other help centers are importing from {host} right now, and we read it for at most 2 at once. Try again in a few minutes.

host_daily_limit

The site has had 20,000 of the importer's requests today, across every help center importing from it.

We have read {host} as often today as we read one site in a day. Try again after 00:00 UTC, or our migration team can move it for you instead, free.

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.

PUT/v1/imports/{importId}

Choose the categories and languages to bring, and whether the articles arrive as drafts. Starts nothing.

Field

Type

Description

category_ids

array of strings

Required. Ids from plan.categories, each as a JSON string: a number is refused. Each brings its category and everything under it.

languages

array of strings

Required. Codes from plan.languages whose lands_in is not null. The source's main language is always imported, even when you leave it out.

arrive_as

string

draft (the default): every article arrives as a draft. as_is: articles that are published at the source and visible to everyone there arrive published, and the rest as drafts. Left out, it stays as it was.

  • Send category_ids and languages on every PUT. An empty list answers 422 with Choose at least one category to import. or Choose at least one language to import.; a value from outside the plan, with Choose categories from the import's plan (plan.categories[].id). or Choose 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 422 on languages: 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: false has no plan to choose from, so every PUT answers 422.

  • The status doesn't change. While the import is queued or running, the answer is 409 with import_running; for a cancelled import, 409 with import_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.

POST/v1/imports/{importId}/run

Start the import with what is chosen. The run happens in the background: follow it with GET /v1/imports/{importId}.

  • Running a completed or failed import runs it again: an article it brought before is updated, never added a second time, and progress then 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 409 with import_running, so a second click does nothing. A cancelled one answers 409 with import_cancelled: scan its address again to set it up anew. One with importable: false answers 409 with not_importable.

  • When the help center's main language changed after the import wrote into it, the answer is 409 with main_language_changed and the message in refusal: 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.

GET/v1/imports/{importId}

Get an import: its plan, what is chosen, how the last run went, and the articles that wait for review.

status

Meaning

scanned

Scanned, not run yet.

scheduled

Set up in the dashboard to run later. Running it from the API starts it now.

queued

Waiting to run. A large import also goes back to queued for a few seconds between two runs, and an import waits here while its source site is busy with other help centers' imports or has had its requests for the day: keep following it.

running

Importing.

completed

Everything you chose is in.

failed

The run stopped. error says why. What arrived stays; run the import again to retry.

cancelled

Cancelled with DELETE, or by a scan of another address before it ran.

assisted

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

total

The chosen articles, as the scan counted them in the main language.

done

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.

created, updated

Articles created, and articles brought before that were updated.

conflicts

Articles that changed both at the source and here since the last run. Your version is kept.

removed

Articles brought before that are no longer at the source. They stay here.

images, image_failures

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_as says, 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 no external_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_in names, 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 with as_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.

DELETE/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.

GET/v1/imports

List 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).

GET/v1/imports/{importId}/redirect-map

Get 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/bulk takes. Read ?page=2 and on, up to meta.total_pages; meta.items_count counts the entries on every page. A page past the last has an empty redirects.

  • In the bulk endpoint's shape. Each entry is {"from", "to"}: from is the old address's path, to the 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_out lists, whole on every page, each old address the map can't give, as {"source_url", "reason"}, where source_url is the full old address. reason is source_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) or source_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

401

"status": "unauthorized"

No key or token, or one the API doesn't accept.

403

insufficient_scope

A Read only key, or an OAuth token without content.write.

403

You are not allowed to import content into this help center with this credential. It needs the content.write scope and, for an OAuth token, a person who administers the help center.

An OAuth token whose person writes content but isn't an Owner or Admin of the help center, such as an Editor.

403

role_forbidden

An OAuth token whose person's role doesn't write content, such as a Viewer or Translator. role names it.

404

No import with this id on this help center.

An id that doesn't exist or belongs to another help center.

409

import_running

Choosing, running or cancelling while the import is queued or running.

409

import_cancelled

Choosing or running a cancelled import, or one handed to the migration team.

409

not_importable

Running an import whose importable is false.

409

main_language_changed

Running after the help center's main language changed.

422

validation_error

A field is missing or invalid. errors names each one.

422

A scan code, such as needs_credentials or not_public

The scan can't read the source. See "Scan a help center" above.

429

{"message": "Too Many Attempts."}

An 11th scan or run in a minute, or another rate limit. Wait Retry-After seconds.

See Errors for the shapes every endpoint shares.

Was this article helpful?