# ZoomHub API reference: Guides Part of the [ZoomHub API reference](https://zoomhub.net/docs/api.md), whose index covers authentication, errors and the other resources. > **Beta:** The v2 API is in beta: endpoints, attributes and error codes may still change before it is final. Questions or feedback: [daniel@zoomhub.net](mailto:daniel@zoomhub.net). Base URL: `https://api.zoomhub.net`. Send your API key as `Authorization: Bearer `; see [Authentication](https://zoomhub.net/docs/api.md#authentication). ## Guides Each guide is a complete program, in curl, JavaScript and Python, that you can save and run as it is. Its steps walk through the program part by part, and link to the endpoints each part calls. The programs read your key from the `ZOOMHUB_API_KEY` environment variable. Paste a key into “Use my key” to write it into the programs instead, then keep the copies to yourself. ### Upload an image and embed it Uploads an image from your computer, waits until it is zoomable, and prints the snippet that embeds it. You need an API key and an image of at most 5 GB: GIF, JPEG, PNG, TIFF or WebP. Run the program with the image’s path. The curl program needs bash, curl and [jq](https://jqlang.org/); the JavaScript one, Node.js 20 or later; the Python one, Python 3.8 or later and [Requests](https://requests.readthedocs.io/). The first lines of each say how to run it. #### 1. Set up Every request sends your key in the `Authorization` header (see [Authentication](https://zoomhub.net/docs/api.md#authentication)). A helper turns any answer but a `2xx` into an error, so the program stops at the first refusal and shows it; the [error object](https://zoomhub.net/docs/api.md#errors) says what went wrong. ```bash set -euo pipefail api="https://api.zoomhub.net" auth="Authorization: Bearer $ZOOMHUB_API_KEY" if [ $# -ne 1 ]; then echo "Usage: bash upload-and-embed.sh " >&2 exit 1 fi file=$1 # Prints the body of a 2xx answer; prints any other answer and fails. request() { local response status response=$(curl --silent --show-error --write-out '\n%{http_code}' "$@") || return status=${response##*$'\n'} if [ "$status" -lt 200 ] || [ "$status" -gt 299 ]; then echo "HTTP $status: ${response%$'\n'*}" >&2 return 1 fi printf '%s\n' "${response%$'\n'*}" } ``` #### 2. Create an upload [Create an upload](https://zoomhub.net/docs/api/uploads.md#uploads-create) with the file’s name, media type and size. The [upload](https://zoomhub.net/docs/api/uploads.md#object-upload) it answers with lists the file’s `parts`: one, by default, with the URL to send the whole file to. The URL works for a day. Calls: `POST /v2/uploads` ```bash case "$(printf '%s' "${file##*.}" | tr '[:upper:]' '[:lower:]')" in gif) content_type="image/gif" ;; jpeg | jpg) content_type="image/jpeg" ;; png) content_type="image/png" ;; tif | tiff) content_type="image/tiff" ;; webp) content_type="image/webp" ;; *) echo "Not an image ZoomHub takes: $file" >&2 exit 1 ;; esac filename=$(basename "$file") body=$(jq --null-input --arg filename "$filename" --arg content_type "$content_type" \ --argjson size_bytes "$(wc -c <"$file")" \ '{filename: $filename, content_type: $content_type, size_bytes: $size_bytes}') upload=$(request --header "$auth" --header "Content-Type: application/json" \ --data "$body" "$api/v2/uploads") ``` #### 3. Send the file to storage `PUT` the file’s bytes to the part’s `url`, with its length. Storage answers `200`. > **Note:** The part’s `url` is storage’s, not ZoomHub’s: don’t send your key there. ```bash echo "Uploading $filename ($content_type)…" >&2 request --upload-file "$file" "$(jq --raw-output '.parts[0].url' <<<"$upload")" >/dev/null ``` #### 4. Complete the upload [Complete an upload](https://zoomhub.net/docs/api/uploads.md#uploads-complete) once the file is in storage. That makes it an [asset](https://zoomhub.net/docs/api/assets.md#object-asset), which is yours and starts processing at once; the completed upload names it as `asset_id`. A body could set the asset’s title and other settings; this program sends none. Calls: `POST /v2/uploads/{id}/complete` ```bash echo "Uploaded. Completing the upload…" >&2 upload=$(request --request POST --header "$auth" \ "$api/v2/uploads/$(jq --raw-output .id <<<"$upload")/complete") id=$(jq --raw-output .asset_id <<<"$upload") ``` #### 5. Wait until it is zoomable Poll [Retrieve an asset](https://zoomhub.net/docs/api/assets.md#assets-retrieve) until its `status` is `succeeded` or `failed`, waiting twice as long after each attempt, up to 30 seconds. `progress` goes from `0` to `1` on the way. Processing takes from seconds to minutes. The program gives up after 30 minutes; the asset stays, so you can look it up later by its ID. Calls: `GET /v2/assets/{id}` ```bash deadline=$((SECONDS + 30 * 60)) delay=1 asset=$(request --header "$auth" "$api/v2/assets/$id") until [ "$(jq --raw-output .status <<<"$asset")" = succeeded ]; do if [ "$(jq --raw-output .status <<<"$asset")" = failed ]; then echo "Processing $id failed." >&2 exit 1 fi if [ "$SECONDS" -gt "$deadline" ]; then echo "$id is still processing. Check on it later." >&2 exit 1 fi echo "Processing $id: $(jq '.progress * 100 | floor' <<<"$asset")%" >&2 sleep "$delay" delay=$((delay * 2 > 30 ? 30 : delay * 2)) asset=$(request --header "$auth" "$api/v2/assets/$id") done ``` #### 6. Embed it `links.embed` is a script that puts a viewer for the image where its `" ``` The page shows this step’s result live. **Example run** ```text Uploading photo.jpg (image/jpeg)… Uploaded. Completing the upload… Processing smpAsset1: 0% Processing smpAsset1: 40% Ready: https://zoomhub.net/a/smpAsset1 Embed it with: ``` ### Turn image URLs into a gallery Makes images on the web zoomable, gathers them into a collection, and prints the snippet that embeds a gallery of it. You need an API key and the URLs of a few images. Run the program with the URLs, in the order the gallery should show them. The curl program needs bash, curl and [jq](https://jqlang.org/); the JavaScript one, Node.js 20 or later; the Python one, Python 3.8 or later and [Requests](https://requests.readthedocs.io/). The first lines of each say how to run it. > **Note:** A PDF of more than one page becomes a collection of its pages by itself. Submit its URL as in *Submit each image* and, once it has succeeded, read its `links.collection`: the collection’s URL, which ends in the collection’s ID. From *Wait for the collection* on, use that ID. #### 1. Set up Every request sends your key in the `Authorization` header (see [Authentication](https://zoomhub.net/docs/api.md#authentication)). A helper turns any answer but a `2xx` into an error, so the program stops at the first refusal and shows it; the [error object](https://zoomhub.net/docs/api.md#errors) says what went wrong. ```bash set -euo pipefail api="https://api.zoomhub.net" auth="Authorization: Bearer $ZOOMHUB_API_KEY" if [ $# -eq 0 ]; then echo "Usage: bash gallery-from-urls.sh …" >&2 exit 1 fi # Prints the body of a 2xx answer; prints any other answer and fails. request() { local response status response=$(curl --silent --show-error --write-out '\n%{http_code}' "$@") || return status=${response##*$'\n'} if [ "$status" -lt 200 ] || [ "$status" -gt 299 ]; then echo "HTTP $status: ${response%$'\n'*}" >&2 return 1 fi printf '%s\n' "${response%$'\n'*}" } ``` #### 2. Submit each image [Create an asset](https://zoomhub.net/docs/api/assets.md#assets-create) makes an image on the web zoomable, and answers with its [asset object](https://zoomhub.net/docs/api/assets.md#object-asset). Its `id` is what a collection lists. The images start processing at once, and needn’t be done to join a collection. > **Note:** The URL of one of your assets answers with that asset, so running the program again is safe. A URL someone else submitted is refused with `409`, since only your own images can join your collection. Calls: `POST /v2/assets` ```bash asset_ids=() for url in "$@"; do body=$(jq --null-input --arg url "$url" '{source: {type: "url", url: {original_url: $url}}}') asset=$(request --header "$auth" --header "Content-Type: application/json" \ --data "$body" "$api/v2/assets") id=$(jq --raw-output .id <<<"$asset") echo "Submitted $url as $id" >&2 asset_ids+=("$id") done ``` #### 3. Create the collection [Create a collection](https://zoomhub.net/docs/api/collections.md#collections-create) with the images under `items`, each as an `asset_id`, in the order to show them. A collection on Business holds up to 1,000 images. Calls: `POST /v2/collections` ```bash body=$(jq --null-input \ '{title: "My gallery", items: [$ARGS.positional[] | {asset_id: .}]}' \ --args "${asset_ids[@]}") collection=$(request --header "$auth" --header "Content-Type: application/json" \ --data "$body" "$api/v2/collections") collection_id=$(jq --raw-output .id <<<"$collection") ``` #### 4. Wait for the collection Poll [Retrieve a collection](https://zoomhub.net/docs/api/collections.md#collections-retrieve) until its `status` is `succeeded`, once every image is processed, or `failed`, once they all finished and at least one failed. Until then it is `processing`. Calls: `GET /v2/collections/{id}` ```bash deadline=$((SECONDS + 30 * 60)) delay=1 until [ "$(jq --raw-output .status <<<"$collection")" = succeeded ]; do if [ "$(jq --raw-output .status <<<"$collection")" = failed ]; then echo "An image in collection $collection_id failed to process." >&2 exit 1 fi if [ "$SECONDS" -gt "$deadline" ]; then echo "$collection_id is still processing. Check on it later." >&2 exit 1 fi echo "Processing collection ${collection_id}…" >&2 sleep "$delay" delay=$((delay * 2 > 30 ? 30 : delay * 2)) collection=$(request --header "$auth" "$api/v2/collections/$collection_id") done ``` #### 5. Create a gallery A gallery is a [viewer](https://zoomhub.net/docs/api/viewers.md#object-viewer) that shows a collection. [Create a viewer](https://zoomhub.net/docs/api/viewers.md#viewers-create) of type `gallery` with the collection’s ID and the options you want; the rest take their defaults. This one lays the images out in a grid and numbers them. Calls: `POST /v2/viewers` ```bash body=$(jq --null-input --arg collection_id "$collection_id" \ '{type: "gallery", title: "My gallery", gallery: {collection_id: $collection_id, layout: "grid", navigation: "numbers"}}') viewer=$(request --header "$auth" --header "Content-Type: application/json" \ --data "$body" "$api/v2/viewers") ``` #### 6. Embed it `links.script` is a script that puts the gallery where its `" ``` **Example answer** ```json { "gallery": { "arrows": "controls", "autoplay": null, "background": "black", "captions": "shown", "collection_id": "Smp7Set9Abc", "controls": "full", "layout": "grid", "motion": "drift", "navigation": "numbers" }, "id": "Smp7View9Ab", "links": { "embed": "https://zoomhub.net/v/Smp7View9Ab/embed", "script": "https://zoomhub.net/v/Smp7View9Ab.js", "self": "https://api.zoomhub.net/v2/viewers/Smp7View9Ab", "share": "https://zoomhub.net/v/Smp7View9Ab" }, "object": "viewer", "title": "My gallery", "type": "gallery" } ``` **Embed** ```html ``` **Example run** ```text Submitted https://example.com/one.jpg as smpAsset1 Submitted https://example.com/two.jpg as smpAsset2 Processing collection Smp7Set9Abc… Processing collection Smp7Set9Abc… Ready: https://zoomhub.net/v/Smp7View9Ab Embed it with: ``` ### Configure and embed a viewer Creates a viewer for one of your images, changes one of its options, lists the image’s viewers, and prints the snippet that embeds the new one. You need an API key and the ID of an image of yours, such as the one *Upload an image and embed it* prints. Run the program with the ID. The curl program needs bash, curl and [jq](https://jqlang.org/); the JavaScript one, Node.js 20 or later; the Python one, Python 3.8 or later and [Requests](https://requests.readthedocs.io/). The first lines of each say how to run it. #### 1. Set up Every request sends your key in the `Authorization` header (see [Authentication](https://zoomhub.net/docs/api.md#authentication)). A helper turns any answer but a `2xx` into an error, so the program stops at the first refusal and shows it; the [error object](https://zoomhub.net/docs/api.md#errors) says what went wrong. ```bash set -euo pipefail api="https://api.zoomhub.net" auth="Authorization: Bearer $ZOOMHUB_API_KEY" if [ $# -ne 1 ]; then echo "Usage: bash configure-viewer.sh " >&2 exit 1 fi content_id=$1 # Prints the body of a 2xx answer; prints any other answer and fails. request() { local response status response=$(curl --silent --show-error --write-out '\n%{http_code}' "$@") || return status=${response##*$'\n'} if [ "$status" -lt 200 ] || [ "$status" -gt 299 ]; then echo "HTTP $status: ${response%$'\n'*}" >&2 return 1 fi printf '%s\n' "${response%$'\n'*}" } ``` #### 2. Create an image viewer [Create a viewer](https://zoomhub.net/docs/api/viewers.md#viewers-create) of type `image` with the image’s ID and the options to set; the rest take their defaults. This one fills its frame with the image, shows the navigator, offers three controls, and highlights in blue. Calls: `POST /v2/viewers` ```bash body=$(jq --null-input --arg content_id "$content_id" '{ type: "image", title: "My viewer", image: { content_id: $content_id, fit: "cover", minimap: "shown", controls: ["zoom", "reset", "fullscreen"], accent_color: "#3a8fd9" } }') viewer=$(request --header "$auth" --header "Content-Type: application/json" \ --data "$body" "$api/v2/viewers") viewer_id=$(jq --raw-output .id <<<"$viewer") echo "Created viewer $viewer_id" >&2 ``` #### 3. Change an option [Update a viewer](https://zoomhub.net/docs/api/viewers.md#viewers-update) with only what changes, under the viewer’s type: every other option keeps its value. Every embed of the viewer follows. Calls: `PATCH /v2/viewers/{id}` ```bash viewer=$(request --request PATCH --header "$auth" --header "Content-Type: application/json" \ --data '{"image": {"background": "white"}}' "$api/v2/viewers/$viewer_id") echo "Background: $(jq --raw-output .image.background <<<"$viewer")" >&2 ``` #### 4. List the image’s viewers [List viewers](https://zoomhub.net/docs/api/viewers.md#viewers-list) with `content_id` answers with every viewer of the image, the new one among them. Calls: `GET /v2/viewers` ```bash echo "Viewers of $content_id:" >&2 request --header "$auth" "$api/v2/viewers?content_id=$content_id" | jq --raw-output '.[] | " \(.id) \(.title // "(untitled)")"' >&2 ``` #### 5. Embed it `links.script` is a script that puts the viewer where its `" ``` **Example answer** ```json { "id": "Smp7View9Ab", "image": { "accent_color": "#3a8fd9", "background": "white", "content_id": "Smp7Pic", "controls": [ "zoom", "reset", "fullscreen" ], "fit": "cover", "heading": "mark", "metadata": "shown", "minimap": "shown" }, "links": { "embed": "https://zoomhub.net/v/Smp7View9Ab/embed", "script": "https://zoomhub.net/v/Smp7View9Ab.js", "self": "https://api.zoomhub.net/v2/viewers/Smp7View9Ab", "share": "https://zoomhub.net/v/Smp7View9Ab" }, "object": "viewer", "title": "My viewer", "type": "image" } ``` **Embed** ```html ``` **Example run** ```text Created viewer Smp7View9Ab Background: white Viewers of Smp7Pic: Smp7View9Ab My viewer Ready: https://zoomhub.net/v/Smp7View9Ab Embed it with: ```