Turn a screenshot into a URL by saving a PNG, posting it to https://imgd.dev/v1/upload, and reading url from the JSON response. Use screencapture on macOS. Use gnome-screenshot on a GNOME Linux desktop. The final command prints a URL for a chat, issue, or terminal report.

Before you capture and upload

imgd.dev costs $1 per GB as a one-time storage purchase. It has no free tier. Every imgd.dev image is public to anyone with its URL. Remove passwords, tokens, customer data, and private messages before capture.

Both procedures need Bash, cURL, jq, and a funded imgd.dev account. Keep IMGD_KEY in a secret manager. Export it into the current shell without printing it.

macOS: capture an area and print its URL

macOS includes screencapture. This script opens the area selector, saves a PNG, uploads it, and prints two useful forms.

#!/usr/bin/env bash
set -eu

: "${IMGD_KEY:?Set IMGD_KEY in your shell.}"
WORK_DIR="$(mktemp -d)"
SCREENSHOT_PATH="$WORK_DIR/screenshot.png"
RESPONSE_PATH="$WORK_DIR/response.json"
trap 'rm -rf "$WORK_DIR"' EXIT

screencapture -i -x "$SCREENSHOT_PATH"
if [ ! -s "$SCREENSHOT_PATH" ]; then
  printf 'No screenshot was saved.\n' >&2
  exit 1
fi

HTTP_STATUS="$(
  curl --silent --show-error \
    --output "$RESPONSE_PATH" \
    --write-out '%{http_code}' \
    --request POST 'https://imgd.dev/v1/upload' \
    --header "Authorization: Bearer $IMGD_KEY" \
    --form "file=@$SCREENSHOT_PATH"
)"

case "$HTTP_STATUS" in
  200|202)
    ;;
  *)
    jq . "$RESPONSE_PATH" >&2
    exit 1
    ;;
esac

HASH="$(jq --exit-status --raw-output '.hash' "$RESPONSE_PATH")"
IMAGE_URL="$(jq --exit-status --raw-output '.url' "$RESPONSE_PATH")"

curl --fail --silent --show-error \
  "https://imgd.dev/v1/images/$HASH" \
  --header "Authorization: Bearer $IMGD_KEY" \
  >/dev/null
PUBLIC_STATUS="$(
  curl --silent --show-error \
    --output /dev/null \
    --write-out '%{http_code}' \
    "$IMAGE_URL"
)"
test "$PUBLIC_STATUS" = "200"

printf '%s\n' "$IMAGE_URL"
printf '[Screenshot](%s)\n' "$IMAGE_URL"

Press Escape to cancel the selector. The script then stops before any upload.

To put only the URL on the macOS clipboard, replace the first printf command with this command:

printf '%s' "$IMAGE_URL" | pbcopy

Linux: capture an area and print its URL

This path uses gnome-screenshot. Install that tool from your Linux distribution if the command is absent.

#!/usr/bin/env bash
set -eu

: "${IMGD_KEY:?Set IMGD_KEY in your shell.}"
command -v gnome-screenshot >/dev/null 2>&1 || {
  printf 'Install gnome-screenshot, then run this script again.\n' >&2
  exit 1
}

WORK_DIR="$(mktemp -d)"
SCREENSHOT_PATH="$WORK_DIR/screenshot.png"
RESPONSE_PATH="$WORK_DIR/response.json"
trap 'rm -rf "$WORK_DIR"' EXIT

gnome-screenshot --area --file="$SCREENSHOT_PATH"
if [ ! -s "$SCREENSHOT_PATH" ]; then
  printf 'No screenshot was saved.\n' >&2
  exit 1
fi

HTTP_STATUS="$(
  curl --silent --show-error \
    --output "$RESPONSE_PATH" \
    --write-out '%{http_code}' \
    --request POST 'https://imgd.dev/v1/upload' \
    --header "Authorization: Bearer $IMGD_KEY" \
    --form "file=@$SCREENSHOT_PATH"
)"

case "$HTTP_STATUS" in
  200|202)
    ;;
  *)
    jq . "$RESPONSE_PATH" >&2
    exit 1
    ;;
esac

HASH="$(jq --exit-status --raw-output '.hash' "$RESPONSE_PATH")"
IMAGE_URL="$(jq --exit-status --raw-output '.url' "$RESPONSE_PATH")"

curl --fail --silent --show-error \
  "https://imgd.dev/v1/images/$HASH" \
  --header "Authorization: Bearer $IMGD_KEY" \
  >/dev/null
PUBLIC_STATUS="$(
  curl --silent --show-error \
    --output /dev/null \
    --write-out '%{http_code}' \
    "$IMAGE_URL"
)"
test "$PUBLIC_STATUS" = "200"

printf '%s\n' "$IMAGE_URL"
printf '[Screenshot](%s)\n' "$IMAGE_URL"

On a Wayland desktop without GNOME, use the desktop screenshot tool to create a PNG. Then run the upload part with that file path.

Read the upload response

A new screenshot returns HTTP 202. Identical bytes can return HTTP 200 with deduplicated: true.

{
  "hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "url": "https://i.imgd.dev/i/0000000000000000000000000000000000000000000000000000000000000000",
  "status": "processing",
  "mime": "image/png",
  "bytes": 98432,
  "width": 1440,
  "height": 900,
  "alt_text": null,
  "unpublish_at": null,
  "note": "the url works immediately, serving a blurred placeholder until the moderation check finishes and it sharpens to the real image"
}

The URL works immediately. During processing, it serves a blurred placeholder. The placeholder protects the original bytes until the automated check ends.

Use the URL in a report

The first output line is suitable for a chat or a terminal report:

https://i.imgd.dev/i/0000000000000000000000000000000000000000000000000000000000000000

The second output line is suitable for a Markdown issue:

[Screenshot](https://i.imgd.dev/i/0000000000000000000000000000000000000000000000000000000000000000)

The link is public. Anyone who can read the chat or issue can request the image.

Verify the upload and URL

Both scripts perform these checks before cleanup. Use the returned hash to inspect the complete metadata:

curl --fail --silent --show-error \
  "https://imgd.dev/v1/images/$HASH" \
  --header "Authorization: Bearer $IMGD_KEY" \
  | jq .

A completed metadata response can contain this JSON:

{
  "hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "url": "https://i.imgd.dev/i/0000000000000000000000000000000000000000000000000000000000000000",
  "status": "live",
  "mime": "image/png",
  "bytes": 98432,
  "width": 1440,
  "height": 900,
  "alt_text": "A terminal window with a failed command and its error message.",
  "category": "safe",
  "nsfw": false,
  "violence": false,
  "filename": "screenshot.png",
  "unpublish_at": null,
  "published": true,
  "created_at": 1787184000
}

Then verify that the public URL returns HTTP 200:

PUBLIC_STATUS="$(
  curl --silent --show-error \
    --output /dev/null \
    --write-out '%{http_code}' \
    "$IMAGE_URL"
)"
test "$PUBLIC_STATUS" = "200"

A review state is terminal and stays blurred. A blocked or error state makes the public URL unavailable.

Recover from common errors

Read the server fix value before you act. The scripts print the complete JSON error to standard error.

Status Cause Action
401 IMGD_KEY is missing or invalid. Load the correct key from the secret manager. Do not print it.
402 The account is unpaid or lacks space. Buy more one-time storage, or delete images. Then retry.
413 The screenshot exceeds 20 MB. Capture a smaller area, or compress the PNG below 20 MB.
415 The file type is not accepted. Save JPEG, PNG, GIF, WebP, or AVIF. Send the matching media type.

If cURL loses the response, send the same file again. Content addressing makes that retry safe. The same bytes produce the same public URL.

If capture fails, check desktop permissions. macOS can require Screen Recording permission for Terminal. Linux can require access through the current desktop session.

Security rules for screenshots

Review the selected area before you release the mouse. Hide environment values, browser sessions, personal messages, and production identifiers.

Do not use this process for evidence that must stay private. Unpublishing can hide a link later, but it does not make public storage confidential.

Decide if this process fits

This process fits public bug reports, support chats, and terminal reports. It works well when a stable URL is more useful than an attached file.

It does not fit private reports, secret test data, video, unsupported files, or images above 20 MB. Use the service evaluation guide for a full decision.

Result

The script prints a plain public URL and a Markdown link. Paste either value into the target chat, issue, or terminal report.

For a lower-level request, read the cURL upload guide. For an agent command, read the coding-agent screenshot guide.

Give an agent the integration procedure. A person can start from the home page.