Upload the image once, read url from the JSON response, then use ![useful alt text](public-url) in the Markdown file. Repository readers can fetch the public URL without an imgd.dev key.

imgd.dev charges a one-time $1 per GB of storage. It has no free tier, so fund the account before the first upload. Every imgd.dev image is public to anyone with its URL. Do not use this method for private screenshots or confidential documentation.

When an external URL helps

A content-addressed image URL does not change while the stored bytes remain available. This property fits README files, release notes, issue templates, and public reports.

GitHub recommends relative image paths for images stored in the same repository. Use a relative path when repository storage and repository permissions fit your needs.

Use an external URL when you want one public image shared across several repositories or documents. The image remains outside the Git repository history.

Upload the image

Run these commands from the repository root. Keep IMGD_KEY in the environment and outside version control.

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=@docs/images/request-volume.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 image 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 upload can return HTTP 200. That response also contains "deduplicated": true.

The URL works immediately. It can show a blurred image during moderation. The same URL shows the final image after a live result.

Insert the Markdown image

Validate the returned host and hash before you edit the README:

IMAGE_URL="$(
  jq -er \
    '.url | select(test("^https://i\\.imgd\\.dev/i/[0-9a-f]{64}$"))' \
    upload.json
)"
printf '%s\n' "$IMAGE_URL"

Use specific alt text that gives the image meaning. Do not leave the alt text empty.

![Line chart of monthly API requests from January through June](https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef)

Append the image from the command line when that edit fits your file:

README_PATH="README.md"
ALT_TEXT="Line chart of monthly API requests from January through June"
printf '\n![%s](%s)\n' "$ALT_TEXT" "$IMAGE_URL" >> "$README_PATH"

The file path is relative to the repository root. Use another relative path, such as docs/report.md, when you edit a nested document.

Verify the README change

First, verify the public image response:

curl --fail --silent --show-error \
  -D image.headers \
  -o /dev/null \
  "$IMAGE_URL"
grep -i '^content-type: image/' image.headers

Then inspect the exact Markdown change:

git diff --check -- README.md
git diff -- README.md

Use the GitHub preview before you commit. Confirm that the image loads and the alt text describes its purpose.

A repository reader does not send IMGD_KEY. The reader fetches the public image URL directly, or through a platform image proxy.

Verify the image metadata

Use the authenticated metadata endpoint when you need the moderation state and generated alt text:

HASH="$(jq -r '.hash' upload.json)"
curl --fail --silent --show-error \
  -H "Authorization: Bearer $IMGD_KEY" \
  "https://imgd.dev/v1/images/$HASH" | jq .

A live result can contain this JSON:

{
  "hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "url": "https://i.imgd.dev/i/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "status": "live",
  "mime": "image/png",
  "bytes": 18452,
  "width": 1200,
  "height": 800,
  "alt_text": "A line chart that shows monthly API requests rising from January through June.",
  "category": "safe",
  "nsfw": false,
  "violence": false,
  "filename": "request-volume.png",
  "unpublish_at": null,
  "published": true,
  "created_at": 1787184000
}

A review result is terminal and stays blurred. A blocked result removes the bytes. Remove a blocked URL from the README.

Recover from upload errors

Each API error contains a stable error value and a direct fix instruction.

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

A representative 415 response is:

{
  "error": "unsupported_media_type",
  "fix": "send jpeg, png, gif, webp, or avif and set the matching Content-Type",
  "accepted": ["image/jpeg", "image/png", "image/gif", "image/webp", "image/avif"]
}

Do not retry a 401, 413, or 415 response without the required change.

Security and maintenance notes

  • Keep IMGD_KEY in an environment variable or a secret manager.
  • Never put the key in Markdown, a URL, Git history, or a log.
  • Remove sensitive EXIF or location data before upload.
  • Treat the image URL as public, even in a private repository.
  • Use useful alt text for readers who cannot load or see the image.
  • Keep the original local source under your normal backup policy.
  • Delete the imgd.dev image when you must free its storage quota.

Deleting the image makes the public URL stop serving. A README that still uses that URL will show a broken image.

When imgd.dev fits

imgd.dev fits public project images that need one stable URL across several Markdown files. It also avoids binary changes in the Git history.

It does not fit private images, repository-only access, a free trial, video, or files above 20 MB. Store sensitive images in an access-controlled system.

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