---
title: "How to Let Claude Code, Codex, or Cursor Upload Screenshots"
description: "Give any coding agent one portable shell contract that uploads a screenshot and returns only its public URL."
slug: "ai-coding-agent-upload-screenshot"
date: 2026-08-20
updated: 2026-08-20
last_tested: 2026-08-20
summary: "Expose IMGD_KEY through the host environment and give the coding agent a shell script that accepts one image path and prints one public URL."
cluster: Automation workflows
intent: workflow
sources:
  - title: "imgd.dev integration procedure"
    url: "https://imgd.dev/integrate.md"
  - title: "imgd.dev OpenAPI specification"
    url: "https://imgd.dev/openapi.json"
  - title: "POSIX shell command language"
    url: "https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html"
---

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.

```sh
#!/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:

```sh
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:

```text
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:

```json
{
  "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:

```sh
SCREENSHOT_PATH='./safe-test.png'
IMAGE_URL="$(./upload-public-image.sh "$SCREENSHOT_PATH")"
printf 'Returned URL: %s\n' "$IMAGE_URL"
```

Confirm these facts:

1. Standard output contains one URL and no other text.
2. Standard error stays empty on success.
3. The URL returns HTTP `200` without a key.
4. The repository and logs do not contain the key.
5. 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](/evaluate.md) 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](/blog/screenshot-to-url-command-line/). For CI failures, read the [Playwright GitHub Actions guide](/blog/playwright-screenshots-github-actions/).

Give an agent the complete [integration procedure](/integrate.md). A person can [start from the home page](/#start).
