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.