Add ?w=800&fmt=webp or ?w=800&fmt=avif to an imgd.dev image URL. The original content-addressed URL remains available without the query string. Wait for a live result before you verify the final format.

imgd.dev charges a one-time $1 per GB of source-image storage. It has no free tier, so fund the account before the first upload. Every original and transformed imgd.dev image is public. Do not transform private or confidential content.

Current accepted transform values

The current image route reads two query values:

  • w sets the output width.
  • fmt sets the output format.

Use a positive whole-number width from 1 through 4096. The route clamps a larger positive numeric value to 4096.

A missing, zero, negative, or nonnumeric w value does not resize the image. Use the documented whole-number range instead of depending on coercion behavior.

The current route accepts these fmt values:

  • webp
  • avif
  • jpeg
  • jpg, as an alias for JPEG
  • png

The route converts the format value to lower case. Use the lower-case forms for clear URLs.

Do not use another fmt value. The current route can fall back to the source type instead of returning a format error.

Upload one source image

Use a source wider than 800 pixels for this example. Keep IMGD_KEY outside the repository.

set -euo pipefail
test -n "$IMGD_KEY"

response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
http_status="$(
  curl --silent --show-error \
    --output "$response_file" \
    --write-out '%{http_code}' \
    -X POST https://imgd.dev/v1/upload \
    -H "Authorization: Bearer $IMGD_KEY" \
    -F "file=@source-image.png;type=image/png"
)"

if [[ "$http_status" != "200" && "$http_status" != "202" ]]; then
  jq . "$response_file" >&2
  exit 1
fi

cp "$response_file" upload.json
jq . upload.json

A new source returns HTTP 202 with this complete JSON shape:

{
  "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "url": "https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "status": "processing",
  "mime": "image/png",
  "bytes": 18452,
  "width": 1200,
  "height": 800,
  "alt_text": null,
  "unpublish_at": null,
  "note": "the url works immediately, serving a blurred placeholder until the moderation check finishes"
}

An identical source can return HTTP 200 with "deduplicated": true.

The immediate URL can serve a blurred JPEG while moderation runs. That temporary response ignores the final transform format.

Wait for the final image state

Poll the authenticated metadata endpoint with a fixed limit:

HASH="$(jq -r '.hash' upload.json)"
for attempt in $(seq 1 20); do
  curl --fail --silent --show-error \
    -H "Authorization: Bearer $IMGD_KEY" \
    "https://imgd.dev/v1/images/$HASH" > metadata.json
  status="$(jq -r '.status' metadata.json)"
  case "$status" in
    live)
      jq . metadata.json
      break
      ;;
    review|blocked|error)
      jq . metadata.json >&2
      exit 1
      ;;
    processing)
      if [[ "$attempt" == "20" ]]; then
        echo "Moderation did not finish within 60 seconds." >&2
        exit 1
      fi
      sleep 3
      ;;
    *)
      echo "Unexpected image status: $status" >&2
      exit 1
      ;;
  esac
done

A review result is terminal and serves a blurred image. A blocked result removes the bytes. Do not build production variants from either result.

Build WebP and AVIF URLs

Validate and keep the original URL:

ORIGINAL_URL="$(
  jq -er \
    '.url | select(test("^https://i\\.imgd\\.dev/i/[0-9a-f]{64}$"))' \
    upload.json
)"
WEBP_URL="${ORIGINAL_URL}?w=800&fmt=webp"
AVIF_URL="${ORIGINAL_URL}?w=800&fmt=avif"

printf 'Original: %s\n' "$ORIGINAL_URL"
printf 'WebP:    %s\n' "$WEBP_URL"
printf 'AVIF:    %s\n' "$AVIF_URL"

The hash path stays the same for all three URLs. Only the query string requests a derivative.

Use the original URL when a client cannot use the requested format. Removing the query string does not modify or replace the stored source.

Verify response headers

Download each derivative and save its headers:

curl --fail --silent --show-error \
  -D resized-webp.headers \
  -o resized.webp \
  "$WEBP_URL"

curl --fail --silent --show-error \
  -D resized-avif.headers \
  -o resized.avif \
  "$AVIF_URL"

grep -i '^content-type: image/webp' resized-webp.headers
grep -i '^content-type: image/avif' resized-avif.headers
file resized.webp resized.avif

The expected media types are:

Content-Type: image/webp
Content-Type: image/avif

Verify that the original still has its source type:

curl --fail --silent --show-error \
  -D original.headers \
  -o original.png \
  "$ORIGINAL_URL"
grep -i '^content-type: image/png' original.headers

Verify output dimensions

If ImageMagick is installed, inspect both derivative files:

identify -format '%m %wx%h\n' resized.webp resized.avif

For a source wider than 800 pixels, each output should report a width of 800. The transform preserves the source aspect ratio when only w is set.

If the source is narrower than your target, select a smaller width for a clear downsize check. Also compare the files visually for acceptable quality.

Other valid URLs

Use the same width rules with JPEG or PNG:

https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=jpeg
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=jpg
https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800&fmt=png

The jpg and jpeg values both return Content-Type: image/jpeg.

You can also request only a resize:

https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef?w=800

That URL keeps the source output type.

Recover from upload errors

Status Meaning Recovery
401 The key is missing or invalid. Set the correct IMGD_KEY, then retry.
402 The account has no storage. Buy storage and verify funding. There is no free tier.
402 The source exceeds the quota. Add the suggested storage or delete unused images.
413 The source exceeds 20 MB. Resize or compress it before upload.
415 The source type is not accepted. Use JPEG, PNG, GIF, WebP, or AVIF with the matching type.
429 The account exceeded the rate limit. Wait for the next minute, then retry.

A representative 413 response is:

{
  "error": "file_too_large",
  "fix": "file exceeds 20MB; resize the image below that and retry",
  "bytes": 24117248,
  "max_bytes": 20971520
}

Recover from transform errors

  • HTTP 400 invalid_hash means the path is not a 64-character lowercase SHA-256 value.
  • HTTP 404 image_not_found means the hash does not exist on this host.
  • HTTP 404 image_unpublished means the owner must publish the same URL again.
  • HTTP 404 image_not_available can mean moderation removed the image bytes.
  • HTTP 409 moderation_pending occurs when a processing URL requests JSON.

If a transform returns the source media type, check the fmt spelling first. Use only the accepted values in this guide.

Security notes

  • Keep IMGD_KEY outside source files, URLs, and logs.
  • Treat the original and all derivative URLs as public.
  • Do not put an access token in a transform query string.
  • Remove sensitive metadata before the source upload.
  • Keep the original URL for format fallback and audits.
  • Delete your claim when you no longer need the source. Another account can keep identical bytes public.

When imgd.dev fits

imgd.dev fits simple public resizing and format conversion from one stable source URL. It supports common documentation, email, and report variants.

It does not fit private transformations, signed URLs, complex crop controls, video, a free trial, or files above 20 MB. Use a full media platform for broader workflows.

Next guides

For an agent, follow the integration procedure. For human setup, open the start page. Read the service evaluation before you select a host.

Sources