REST API reference

Images

Export
Download Markdown Use with AI

Upload the images your articles show and get back a permanent URL to put in article HTML. You can send the file, give the https address of an image for HelpCenter.io to import, or create a single-use upload URL for a process that has the file but not your API key.

An article's content is HTML, so an illustrated article takes two steps: upload the image, then use its URL in an <img> tag when you create or update the article. Every upload response includes a ready-made HTML and Markdown snippet. To skip the first step, write the article with rehost_images and HelpCenter.io copies the images it links to (see Articles).

Endpoints

Method and path

What it does

Scope

POST /v1/images

Upload one image, as a file or from an https address

content.write

POST /v1/images/bulk

Upload up to 20 images in one request

content.write

POST /v1/images/uploads

Create a single-use upload URL

content.write

POST to the upload_url

Upload one image to an upload URL, with no API key

None: the URL is the credential

GET /v1/images

List images, newest first

content.read

GET /v1/images/{imageId}

Get one image

content.read

DELETE /v1/images/{imageId}

Delete an image and its file

content.write

A Read only key has content.read; a Read & write key also has content.write. OAuth access tokens work too, with the same scopes (see Scopes).

The image object

Field

Type

Description

id

integer

The image's id.

url

string

The image's permanent, absolute URL. Use it in article HTML.

filename

string

For an upload, the name of the file you sent. For an import, the last part of the address's path, with the extension of the detected type.

mime_type

string or null

image/jpeg, image/png, image/gif or image/webp, detected from the file itself. null on images stored before HelpCenter.io recorded it.

size

integer

Size in bytes.

checksum

string or null

The SHA-256 of the file, as 64 hex characters. null on images stored before HelpCenter.io recorded it.

alt_text

string

The alt_text you sent with this upload. It is not stored, so every read returns "". Alt text belongs in the article's HTML.

snippets.html

string

An <img> tag with the URL and the alt text, both HTML-escaped.

snippets.markdown

string

A Markdown image with the URL and the alt text.

created_at

string

When the image was stored, as YYYY-MM-DD HH:MM:SS in UTC, with no offset.

This is the answer to a first upload. The image object sits under image, next to reused:

{
  "status": "success",
  "reused": false,
  "image": {
    "id": 3,
    "url": "https://helpcenter-io.s3.amazonaws.com/uploads/acme/76LPXpORQpiIyW5Bf4Wvo7HtHsyBOjgGGH7NNID3.png",
    "filename": "create-board.png",
    "mime_type": "image/png",
    "size": 52409,
    "checksum": "85a038dfadb5e2c6e1f801b31a2ea21b49b9187053571c5df999af581e429e2a",
    "alt_text": "The Create a board article",
    "snippets": {
      "html": "<img src=\"https://helpcenter-io.s3.amazonaws.com/uploads/acme/76LPXpORQpiIyW5Bf4Wvo7HtHsyBOjgGGH7NNID3.png\" alt=\"The Create a board article\">",
      "markdown": "![The Create a board article](https://helpcenter-io.s3.amazonaws.com/uploads/acme/76LPXpORQpiIyW5Bf4Wvo7HtHsyBOjgGGH7NNID3.png)"
    },
    "created_at": "2026-09-30 08:17:20"
  }
}

HelpCenter.io stores the file exactly as you send it: it does not resize, compress or convert images. Images stay in place when you delete an article that uses them.

Upload an image

Send the image as multipart/form-data under file. A new image answers 201 Created; bytes this help center already has answer 200 OK with the existing image.

POST/v1/images

Upload one image file and get back its permanent URL.

Field

Type

Description

file

file

Required unless you send source_url. A JPEG, PNG, GIF or WebP image of up to 10 MB.

source_url

string

Required unless you send file. The https address of an image to import, up to 2,048 characters. See Import from a URL below.

alt_text

string

Optional, up to 255 characters. Used only to build snippets in this response.

dedupe

boolean

Defaults to on. Send 0 to store another copy of bytes this help center already has.

Which files are accepted

  • JPEG, PNG, GIF and WebP, up to 10 MB (10,485,760 bytes) each.

  • The type is read from the file's content, not from its name or the Content-Type you send. A text file renamed to .png is refused.

  • SVG is not accepted, in any form.

The same image twice

Uploads are matched by content. When this help center already has an image with the same bytes, you get that image back with 200 OK and "reused": true, including its original filename, url and created_at. The file name you send plays no part, and an import and an upload of the same bytes resolve to the same image. That makes a failed import safe to run again: the files that already landed come back with the URLs you already have.

To store another copy anyway, send dedupe as 0. In a multipart body a boolean must be 1 or 0: the strings true and false are refused with 422 Unprocessable Entity and The dedupe field must be true or false. In a JSON body, true and false work.

Import from a URL

Instead of the file, send source_url: the https address of an image. HelpCenter.io downloads it and stores it like an upload, duplicate check included. This request can be JSON, so it suits clients that cannot send files, such as AI agents.

POST/v1/images

Import an image from an https address instead of sending the file.

The download follows these rules:

  • The address, and every redirect, must be https. Up to 4 redirects are followed.

  • Addresses that resolve to private or reserved networks are refused.

  • A download, redirects included, has 15 seconds and 10 MB.

  • The type is read from the downloaded bytes, so the file must be a JPEG, PNG, GIF or WebP image whatever the address ends with.

When the address is refused or the download fails, you get 422 with The image at source_url could not be imported. and the reason under errors.source_url, for example The image could not be imported: the image returned HTTP 404. The reasons you are most likely to meet:

Reason

What to check

the host could not be resolved

The host name in the address.

the host resolves to a private or reserved address

The image must be on a public address.

the image returned HTTP 404

The address answered with that status. Any status other than 2xx fails.

the image could not be downloaded in time

The server took longer than 15 seconds.

the image is larger than the 10485760-byte limit

The file is over 10 MB.

the file is not a JPEG, PNG, GIF or WebP image

The address serves another type, such as SVG or an HTML page.

the image redirected to an address that is not https

A redirect went to an http address.

Send either file or source_url, never both.

Errors

Status

Message

When

422

Send the image as multipart/form-data under "file", or its https address as "source_url".

There is no file and no address.

422

Unsupported image type. Upload a JPEG, PNG, GIF, or WebP image.

The file is not one of the accepted types.

422

The image is too large. The maximum upload size is 10 MB.

The file is over 10 MB.

422

The source_url must be an https:// address.

The address is not https.

422

Send either a file or a source_url, not both.

You sent both.

422

The image at source_url could not be imported.

The import failed; the reason is in errors.source_url.

403

This action requires the content.write scope.

The key is Read only, or the token lacks content.write.

500

The image could not be stored. Please try again.

Storage failed. Nothing is left behind, so retry.

Validation errors use the {"status": "validation_error", "message": "The upload could not be accepted.", "errors": {…}} shape, with each message under the field it concerns.

Upload several images

Send up to 20 images in one request: files as files[], addresses to import as source_urls[], or both. The answer is always 207 Multi-Status once the request is valid, with one result per image.

POST/v1/images/bulk

Upload up to 20 image files in one request, with a result for each.

Import several addresses with JSON:

POST/v1/images/bulk

Import up to 20 images from https addresses in one request.

  • Items are matched by position: alt_texts[0] belongs to item 0. To mix files and addresses in one multipart request, give every item its own index, for example files[0] and source_urls[1]. One index cannot carry both.

  • The limit of 20 counts files and addresses together.

  • Every file is checked before anything is stored. One refused file fails the whole request with 422, and nothing is written; the error names the item, such as files.1.

  • Addresses are downloaded one at a time, so a refused or failed address is an error result inside the 207, with code import_failed. All the downloads in one request share 45 seconds.

  • A request body can be at most 32 MB, so batch large files by total size: 20 files of 10 MB do not fit in one request.

Read each entry in results, not the status code. The top-level status is "success" even when every item failed:

Field

Description

results[].index

The item's position in the request.

results[].status

created, reused (the bytes were already stored) or error.

results[].image

The image object, for created and reused.

results[].filename or results[].source_url

On an error, which file or address it was.

results[].error

On an error, code (import_failed or storage_failed) and message.

meta

total, created, reused and failed counts.

More than 20 items answers 422 with Too many images in one request. The maximum is 20 per batch, files and source URLs together. (or the files-only or URLs-only form of the same message).

Create an upload URL

Use an upload URL when the process that has the file should not hold your API key: an AI agent, a browser, a build job. Your server creates the URL with the key, and passes it on. Whoever has the URL can upload one image to this help center, as the credential that created it, without a key of their own.

POST/v1/images/uploads

Create a single-use URL that accepts one image upload without an API key.

  • An upload URL works for 15 minutes (expires_at) and accepts a single image of up to max_bytes bytes (10 MB).

  • It is on api.helpcenter.io, and it is signed: use it exactly as returned.

  • Anyone who has the URL can use it until it is spent or expires, so hand it only to the process that needs it.

Upload to an upload URL

POST the image as multipart/form-data to the exact upload_url, query string included. Send no API key. The fields and the answer are the same as for POST /v1/images, except that file is required (source_url is not read here):

curl -sS -X POST "$UPLOAD_URL" \
  -H "Accept: application/json" \
  -F "file=@help-center-home.png" \
  -F "alt_text=The help center home page"

Quote the URL: it contains &. The response is 201 Created (or 200 OK with "reused": true) with the image, exactly as for POST /v1/images.

POST/v1/images/uploads/{intent}

Upload one image to an upload URL, with no API key: the URL is the credential.

When the URL is refused, the body is {"status": "error", "code": "…", "message": "…"}:

Status

Code

When

403

upload_url_invalid

The URL was changed, or it is not one HelpCenter.io issued. Create a new one.

410

upload_url_expired

The 15 minutes are over. Create a new one.

410

upload_url_used

An image was already uploaded to it. Each URL takes one upload.

403

upload_url_revoked

The key that created it can no longer upload images to this help center, for example because the key was deleted.

A refused file (422) or a storage failure (500) does not spend the URL: correct the file and upload again to the same URL.

List images

List the images uploaded through the API, newest first. The list includes images uploaded with any of the endpoints above, and the copies HelpCenter.io makes when an article is written with rehost_images. Images added in the article editor are not included.

GET/v1/images

List the images uploaded through the API, newest first.

Parameter

Type

Description

limit

integer

Images per page, 1 to 100. Defaults to 50.

page

integer

Page number, starting at 1. Defaults to 1.

checksum

string

Only images whose SHA-256 is exactly this.

To check whether a file is already uploaded, hash it and ask for its checksum. You get the matching images, more than one if copies were stored with dedupe off, or an empty list:

CHECKSUM=$(shasum -a 256 create-board.png | cut -d ' ' -f 1)
curl -sS "https://api.helpcenter.io/v1/images?checksum=$CHECKSUM" \
  -H "apikey: $HELPCENTER_API_KEY" \
  -H "Accept: application/json"

Send Accept: application/json. Without it, an invalid limit, page or checksum is answered with a redirect instead of the 422 error. The meta block counts every matching image: page, per_page, total_pages and items_count (see Pagination).

Get an image

GET/v1/images/{imageId}

Get one image.

An id that belongs to another help center answers 404 with Image not found., the same as an id that does not exist.

Delete an image

Delete an image's record and its file. There is no undo: pages that still show the URL show a broken image.

DELETE/v1/images/{imageId}

Delete an image and its file, unless article content still uses it.

Before deleting, HelpCenter.io checks the current content of this help center's articles, drafts included, for the image's URL. When articles use it, the delete is refused with 409 Conflict and referenced_by lists up to 20 of them, each with its id and title in every language. Update those articles first, or add ?force=true to delete anyway.

The check covers only that current content. Articles in Trash, staged changes and earlier versions are not checked, so an image they use can be deleted without a 409, and shows as broken if you restore or publish that content later.

Rate limits

Every request made with your key or token counts toward its limits: 300 requests a minute, and for deletes also the 120 writes a minute. Uploads have extra limits per IP address:

  • POST /v1/images and POST /v1/images/uploads: 60 a minute.

  • POST /v1/images/bulk: 20 a minute.

  • Uploads to an upload URL: 60 a minute, counted on their own.

The first two share one counter per IP address with POST /v1/articles/bulk and GET /v1/export, and each endpoint compares that shared count with its own limit. So 20 single uploads in a minute make the next bulk request from the same address answer 429 Too Many Requests. Wait the number of seconds in the Retry-After header, then retry. See Rate limits.

Errors

Status

Body

When

401

{"status": "unauthorized"}

The key or token is missing or unknown.

403

insufficient_scope

A Read only key, or a token without content.write, on an upload or delete.

403, 410

upload_url_…

An upload URL was refused. See Upload to an upload URL above.

404

Image not found.

No image with that id in this help center.

409

referenced_by

A delete of an image that articles still use.

422

validation_error

A refused file, address or field.

429

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

A rate limit. Wait for Retry-After.

500

The image could not be stored. Please try again.

Storage failed. Retry.

For the shapes every endpoint shares, see Errors.

Was this article helpful?