Let Claude Code, Codex, Cursor, or another coding agent upload screenshots through one shell contract. The command accepts one local image path. It writes one public URL to standard output. It writes errors to standard error. This method uses normal shell access and does not use a hidden vendor API.
Before you give the command to an agent
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. Do not let an agent upload private source, credentials, customer data, or confidential screenshots.
Store the key in the host secret manager. Inject it as IMGD_KEY when the agent runs. Do not put the key in a prompt, repository, command argument, URL, or log.
The command needs sh, cURL, jq, and mktemp. These tools are available on many macOS and Linux systems.
Use one portable shell contract
Save this script as upload-public-image.sh. The script contains no key, so you can track the script itself.
#!/bin/sh
set -eu
if [ "$#" -ne 1 ]; then
printf 'Usage: %s IMAGE_PATH\n' "$0" >&2
exit 64
fi
: "${IMGD_KEY:?Set IMGD_KEY in the host environment.}"
IMAGE_PATH="$1"
if [ ! -f "$IMAGE_PATH" ] || [ ! -r "$IMAGE_PATH" ] || [ ! -s "$IMAGE_PATH" ]; then
printf 'The image path must be a readable, non-empty file.\n' >&2
exit 66
fi
case "$IMAGE_PATH" in
*.jpg|*.jpeg|*.JPG|*.JPEG)
MEDIA_TYPE='image/jpeg'
;;
*.png|*.PNG)
MEDIA_TYPE='image/png'
;;
*.gif|*.GIF)
MEDIA_TYPE='image/gif'
;;
*.webp|*.WEBP)
MEDIA_TYPE='image/webp'
;;
*.avif|*.AVIF)
MEDIA_TYPE='image/avif'
;;
*)
printf 'Use a JPEG, PNG, GIF, WebP, or AVIF file.\n' >&2
exit 65
;;
esac
RESPONSE_PATH="$(mktemp)"
trap 'rm -f "$RESPONSE_PATH"' 0 HUP INT TERM
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=@$IMAGE_PATH;type=$MEDIA_TYPE"
)"
case "$HTTP_STATUS" in
200|202)
;;
*)
ERROR_CODE="$(jq --raw-output '.error // "unknown_error"' "$RESPONSE_PATH" 2>/dev/null || true)"
FIX="$(jq --raw-output '.fix // "Read the response body."' "$RESPONSE_PATH" 2>/dev/null || true)"
printf 'Upload failed with HTTP %s (%s): %s\n' \
"$HTTP_STATUS" "$ERROR_CODE" "$FIX" >&2
exit 1
;;
esac
if ! HASH="$(jq --exit-status --raw-output '.hash | select(type == "string" and length > 0)' "$RESPONSE_PATH")"; then
printf 'The upload response has no valid hash.\n' >&2
exit 1
fi
if ! IMAGE_URL="$(jq --exit-status --raw-output '.url | select(type == "string" and length > 0)' "$RESPONSE_PATH")"; then
printf 'The upload response has no valid URL.\n' >&2
exit 1
fi
METADATA_STATUS="$(
curl --silent --show-error \
--output /dev/null \
--write-out '%{http_code}' \
"https://imgd.dev/v1/images/$HASH" \
--header "Authorization: Bearer $IMGD_KEY"
)"
if [ "$METADATA_STATUS" != '200' ]; then
printf 'Metadata verification failed with HTTP %s.\n' "$METADATA_STATUS" >&2
exit 1
fi
PUBLIC_STATUS="$(
curl --silent --show-error \
--output /dev/null \
--write-out '%{http_code}' \
"$IMAGE_URL"
)"
if [ "$PUBLIC_STATUS" != '200' ]; then
printf 'Public URL verification failed with HTTP %s.\n' "$PUBLIC_STATUS" >&2
exit 1
fi
printf '%s\n' "$IMAGE_URL"
Make it executable:
chmod 700 upload-public-image.sh
The 700 mode lets only the current user run or change the script. The script still contains no secret.
Give every coding agent the same instruction
Use this tool instruction in the project guidance for any coding agent:
When a user asks for a screenshot URL, capture an approved screenshot to a local file.
Run: IMAGE_URL="$(./upload-public-image.sh "$SCREENSHOT_PATH")"
Return IMAGE_URL to the user.
Do not print IMGD_KEY.
Do not upload an image until you confirm that all image content can be public.
If the command fails, report its standard error and stop.
The agent only needs permission to run the script. The contract does not depend on a Claude Code, Codex, or Cursor extension.
Do not tell the agent to inspect or return IMGD_KEY. The host environment supplies the value directly to the process.
Expected API JSON
The script consumes this success body and prints only its url value:
{
"hash": "0000000000000000000000000000000000000000000000000000000000000000",
"url": "https://i.imgd.dev/i/0000000000000000000000000000000000000000000000000000000000000000",
"status": "processing",
"mime": "image/png",
"bytes": 76321,
"width": 1280,
"height": 720,
"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"
}
New bytes return HTTP 202. Identical bytes return HTTP 200 and can include deduplicated: true.
The public URL works immediately. It returns a blurred placeholder during processing. The metadata can later become live, review, blocked, or error.
Verify the agent result
Run the contract outside the agent first with a safe test image:
SCREENSHOT_PATH='./safe-test.png'
IMAGE_URL="$(./upload-public-image.sh "$SCREENSHOT_PATH")"
printf 'Returned URL: %s\n' "$IMAGE_URL"
Confirm these facts:
- Standard output contains one URL and no other text.
- Standard error stays empty on success.
- The URL returns HTTP
200without a key. - The repository and logs do not contain the key.
- A failed command stops the agent procedure.
The script verifies both the authenticated metadata route and the public image route before it prints the URL.
Recover from API errors
The script surfaces the server error and fix values. It does not make an automatic payment or account change.
| Status | Cause | Required action |
|---|---|---|
401 |
The host did not supply a valid key. | Correct the secret injection. Do not ask the agent to print the key. |
402 |
The account has no storage or its quota is full. | A person or authorized process must buy storage or delete images. |
413 |
The image is larger than 20 MB. | Capture a smaller area or reduce the file below 20 MB. |
415 |
The type is not accepted. | Convert the file to JPEG, PNG, GIF, WebP, or AVIF. |
Do not retry these responses without a change. A network failure can be retried with the same bytes. Content addressing keeps the URL stable and prevents duplicate storage charges.
A review state is terminal and remains blurred. Do not build an endless status loop around this command.
Keep the contract safe
Scope IMGD_KEY to the agent process when your secret system supports that control. Do not enable shell trace output around the command.
Review screenshots before upload. Terminal windows can show tokens, file paths, email addresses, and production data.
The output URL is not a secret. It points to a public image. Do not place private meaning in an unguessable URL.
Decide if this contract fits
This contract fits coding agents that can call a normal shell command. It gives different agents one stable interface and one output shape.
It does not fit agents without shell access, private images, files above 20 MB, unsupported formats, or free-tier requirements. Read the service evaluation guide before use.
Result
Each supported coding agent can call the same script and receive one verified public screenshot URL. No vendor-specific upload API is required.
For manual use, read the command-line screenshot guide. For CI failures, read the Playwright GitHub Actions guide.
Give an agent the complete integration procedure. A person can start from the home page.